///|
fn join_path(base : String, child : String) -> String {
  @path.Path(base).join(@path.Path(child)).to_string()
}

///|
fn config_path(path : String) -> String {
  join_path(path, "moondoccheck.toml")
}

///|
fn load_scan_config(path : String) -> ScanConfig raise {
  let file = config_path(path)
  if @fs.path_exists(file) && @fs.is_file(file) {
    parse_scan_config(@fs.read_file_to_string(file))
  } else {
    default_scan_config()
  }
}

///|
fn collect_files(
  path : String,
  files : Array[String],
  options : ScanOptions,
) -> Unit raise {
  if should_ignore_path_with_exclusions(path, options.exclusions) {
    ()
  } else if @fs.is_file(path) {
    files.push(path)
  } else if @fs.is_dir(path) {
    for child in @fs.read_dir(path) {
      collect_files(join_path(path, child), files, options)
    }
  }
}

///|
fn merge_markdown_stats(
  left : MarkdownStats,
  right : MarkdownStats,
) -> MarkdownStats {
  {
    files: left.files + right.files,
    code_blocks: left.code_blocks + right.code_blocks,
    mbt_check_blocks: left.mbt_check_blocks + right.mbt_check_blocks,
    mbt_nocheck_blocks: left.mbt_nocheck_blocks + right.mbt_nocheck_blocks,
    moonbit_blocks: left.moonbit_blocks + right.moonbit_blocks,
  }
}

///|
fn merge_ci_stats(left : CiStats, right : CiStats) -> CiStats {
  {
    workflow_files: left.workflow_files + right.workflow_files,
    has_moon_check: left.has_moon_check || right.has_moon_check,
    has_moon_test: left.has_moon_test || right.has_moon_test,
    has_moon_fmt: left.has_moon_fmt || right.has_moon_fmt,
    has_moon_info: left.has_moon_info || right.has_moon_info,
    has_moon_run: left.has_moon_run || right.has_moon_run,
    has_script_validation: left.has_script_validation ||
    right.has_script_validation,
  }
}

///|
fn missing_readme_stats() -> ReadmeStats {
  {
    found: false,
    has_overview: false,
    has_usage: false,
    has_code_example: false,
    has_development_commands: false,
    has_license_mention: false,
  }
}

///|
fn missing_moon_mod_stats() -> MoonModStats {
  {
    found: false,
    legacy_json_found: false,
    has_name: false,
    has_version: false,
    has_readme: false,
    has_repository: false,
    has_license: false,
    has_keywords: false,
    has_description: false,
  }
}

///|
fn empty_markdown_stats() -> MarkdownStats {
  {
    files: 0,
    code_blocks: 0,
    mbt_check_blocks: 0,
    mbt_nocheck_blocks: 0,
    moonbit_blocks: 0,
  }
}

///|
fn empty_ci_stats() -> CiStats {
  {
    workflow_files: 0,
    has_moon_check: false,
    has_moon_test: false,
    has_moon_fmt: false,
    has_moon_info: false,
    has_moon_run: false,
    has_script_validation: false,
  }
}

///|
fn build_project_report(
  project_name : String,
  files_scanned : Int,
  moonbit_source_files : Int,
  api_items : Array[ApiItem],
  markdown : MarkdownStats,
  readme : ReadmeStats,
  moon_mod : MoonModStats,
  ci : CiStats,
  has_generated_mbti : Bool,
  config : ScanConfig,
) -> ProjectReport {
  let report = report_from_api_items(project_name, api_items)
  let base = {
    ..report,
    project_root: project_name,
    files_scanned,
    moonbit_source_files,
    markdown,
    readme,
    moon_mod,
    ci,
    has_generated_mbti,
  }
  { ..base, issues: report.issues + project_readiness_issues(base, config) }
}

///|
/// Build default scan options.
pub fn default_scan_options() -> ScanOptions {
  { exclusions: [] }
}

///|
/// Scan a MoonBit project directory with explicit scan options.
pub fn scan_project_with_options(
  path : String,
  options : ScanOptions,
) -> ProjectReport raise {
  let config = load_scan_config(path)
  let options = apply_scan_config(config, options)
  let files : Array[String] = []
  collect_files(path, files, options)
  let api_items : Array[ApiItem] = []
  let mut markdown = empty_markdown_stats()
  let mut readme = missing_readme_stats()
  let mut moon_mod = missing_moon_mod_stats()
  let mut ci = empty_ci_stats()
  let mut moonbit_source_files = 0
  let mut has_generated_mbti = false

  for file in files {
    if is_moonbit_source_file(file) {
      moonbit_source_files += 1
      let source = @fs.read_file_to_string(file)
      api_items.append(scan_moon_source(file, source))
    } else if is_markdown_file(file) {
      let source = @fs.read_file_to_string(file)
      markdown = merge_markdown_stats(markdown, scan_markdown_text(source))
      if is_readme_file(file) {
        readme = scan_readme_text(source)
      }
    } else if file.has_suffix("moon.mod") {
      moon_mod = scan_moon_mod_text(@fs.read_file_to_string(file))
    } else if file.has_suffix("moon.mod.json") {
      moon_mod = scan_moon_mod_json_text(@fs.read_file_to_string(file))
    } else if file.has_suffix("pkg.generated.mbti") {
      has_generated_mbti = true
    } else if is_github_workflow_file(file) {
      ci = merge_ci_stats(ci, scan_ci_text(@fs.read_file_to_string(file)))
    }
  }

  build_project_report(
    path,
    files.length(),
    moonbit_source_files,
    api_items,
    markdown,
    readme,
    moon_mod,
    ci,
    has_generated_mbti,
    config,
  )
}

///|
/// Scan a MoonBit project directory and build a documentation quality report.
pub fn scan_project(path : String) -> ProjectReport raise {
  scan_project_with_options(path, default_scan_options())
}