///|
/// Content validation: broken links, route collisions, missing frontmatter.

///|
/// Detect route collisions in the site.
pub fn check_route_collisions(site : Site) -> Site {
  let route_count : Map[String, Int] = {}
  for route in site.routes {
    let prev = route_count.get_or_default(route, 0)
    route_count.set(route, prev + 1)
  }
  let diagnostics = site.diagnostics
  for route, count in route_count {
    if count > 1 {
      diagnostics.push(
        error(
          E002_ROUTE_COLLISION,
          route,
          None,
          None,
          "Route '\{route}' is produced by \{count} source files.",
        ),
      )
    }
  }
  return { ..site, diagnostics, }
}

///|
/// Check for broken wikilinks across all pages.
pub fn check_broken_links(site : Site) -> Site {
  let diagnostics = site.diagnostics
  let routes_set : @set.Set[String] = @set.Set([], capacity=0)
  for route in site.routes {
    routes_set.add(route)
  }
  for _, page in site.pages {
    for target in page.outgoing_links {
      if !routes_set.contains(target) {
        diagnostics.push(
          error(
            E001_BROKEN_LINK,
            page.metadata.title,
            None,
            None,
            "Broken wikilink in '\{page.route}' → '\{target}' not found.",
          ),
        )
      }
    }
  }
  return { ..site, diagnostics, }
}

///|
/// Check all pages have a non-empty title derived.
pub fn check_missing_titles(site : Site) -> Site {
  let diagnostics = site.diagnostics
  for route, page in site.pages {
    if page.title == "" {
      diagnostics.push(
        warning(
          E003_MISSING_FRONTMATTER,
          route,
          None,
          None,
          "Page at '\{route}' has no title in frontmatter or headings.",
        ),
      )
    }
  }
  return { ..site, diagnostics, }
}

///|
/// Known template variable names available in the theme context.
/// Top-level keys that must exist for proper rendering.
let known_template_vars : Array[String] = [
  "site_title", "site_language", "site_pages", "site_nav", "page_title", "page_content",
  "page_route", "page_description", "page_date", "page_author", "this", "type", "text",
  "url", "children",
]

///|
/// Check that all template variables in the given layouts are known.
/// Returns diagnostics for any unknown variable references.
pub fn check_template_slots(layouts : Map[String, String]) -> Array[Diagnostic] {
  let result : Array[Diagnostic] = []
  for layout_name, template in layouts {
    let unknowns = find_unknown_vars(template)
    for var_name in unknowns {
      result.push(
        warning(
          E005_MISSING_TEMPLATE,
          "theme/layouts/\{layout_name}.html",
          None,
          None,
          "Unknown template variable '\{var_name}' in layout '\{layout_name}'.",
        ),
      )
    }
  }
  result
}

///|
/// Find all {{ variable }} references that are not in the known set.
fn find_unknown_vars(template : String) -> Array[String] {
  let result : Array[String] = []
  let len = template.length()
  let mut pos = 0
  while pos < len {
    let open = find_from(template, "{{", pos)
    match open {
      None => break
      Some(op) => {
        let close = find_from(template, "}}", op + 2)
        match close {
          None => break
          Some(cp) => {
            let inner = template[op + 2:cp].trim().to_owned()
            // skip block tags: #if, #each, /if, /each
            if inner.has_prefix("#") || inner.has_prefix("/") {
              pos = cp + 2
              continue
            }
            // skip this and this.field references
            if inner == "this" || inner.has_prefix("this.") {
              pos = cp + 2
              continue
            }
            // check if it's a known variable
            if !is_known_var(inner) {
              result.push(inner)
            }
            pos = cp + 2
          }
        }
      }
    }
  }
  result
}

///|
/// Check if a variable name is in the known set.
fn is_known_var(name : String) -> Bool {
  for k in known_template_vars {
    if name == k {
      return true
    }
  }
  false
}

///|
fn find_from(s : String, sub : String, start : Int) -> Int? {
  let len = s.length()
  let sub_len = sub.length()
  for i = start; i <= len - sub_len; i = i + 1 {
    if s[i:i + sub_len] == sub {
      return Some(i)
    }
  }
  None
}