import YAML from "yaml";

export type ParsedFrontmatter = Record<string, string>;

type ParsedFrontmatterLineEntry = {
  value: string;
  kind: "inline" | "multiline";
  rawInline: string;
};

type ParsedYamlValue = {
  value: string;
  kind: "scalar" | "structured";
};

function stripQuotes(value: string): string {
  if (
    (value.startsWith('"') && value.endsWith('"')) ||
    (value.startsWith("'") && value.endsWith("'"))
  ) {
    return value.slice(1, -1);
  }
  return value;
}

function coerceYamlFrontmatterValue(value: unknown): ParsedYamlValue | undefined {
  if (value === null || value === undefined) {
    return undefined;
  }
  if (typeof value === "string") {
    return {
      value: value.trim(),
      kind: "scalar",
    };
  }
  if (typeof value === "number" || typeof value === "boolean") {
    return {
      value: String(value),
      kind: "scalar",
    };
  }
  if (typeof value === "object") {
    try {
      return {
        value: JSON.stringify(value),
        kind: "structured",
      };
    } catch {
      return undefined;
    }
  }
  return undefined;
}

function parseYamlFrontmatter(block: string): Record<string, ParsedYamlValue> | null {
  try {
    const parsed = YAML.parse(block, { schema: "core" }) as unknown;
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
      return null;
    }
    const result: Record<string, ParsedYamlValue> = {};
    for (const [rawKey, value] of Object.entries(parsed as Record<string, unknown>)) {
      const key = rawKey.trim();
      if (!key) {
        continue;
      }
      const coerced = coerceYamlFrontmatterValue(value);
      if (!coerced) {
        continue;
      }
      result[key] = coerced;
    }
    return result;
  } catch {
    return null;
  }
}

function extractMultiLineValue(
  lines: string[],
  startIndex: number,
): {
  value: string;
  linesConsumed: number;
} {
  const valueLines: string[] = [];
  let i = startIndex + 1;

  while (i < lines.length) {
    const line = lines[i];
    if (line.length > 0 && !line.startsWith(" ") && !line.startsWith("\t")) {
      break;
    }
    valueLines.push(line);
    i += 1;
  }

  const combined = valueLines.join("\n").trim();
  return { value: combined, linesConsumed: i - startIndex };
}

function parseLineFrontmatter(block: string): Record<string, ParsedFrontmatterLineEntry> {
  const result: Record<string, ParsedFrontmatterLineEntry> = {};
  const lines = block.split("\n");
  let i = 0;

  while (i < lines.length) {
    const line = lines[i];
    const match = line.match(/^([\w-]+):\s*(.*)$/);
    if (!match) {
      i += 1;
      continue;
    }

    const key = match[1];
    const inlineValue = match[2].trim();
    if (!key) {
      i += 1;
      continue;
    }

    if (!inlineValue && i + 1 < lines.length) {
      const nextLine = lines[i + 1];
      if (nextLine.startsWith(" ") || nextLine.startsWith("\t")) {
        const { value, linesConsumed } = extractMultiLineValue(lines, i);
        if (value) {
          result[key] = {
            value,
            kind: "multiline",
            rawInline: inlineValue,
          };
        }
        i += linesConsumed;
        continue;
      }
    }

    const value = stripQuotes(inlineValue);
    if (value) {
      result[key] = {
        value,
        kind: "inline",
        rawInline: inlineValue,
      };
    }
    i += 1;
  }

  return result;
}

function lineFrontmatterToPlain(
  parsed: Record<string, ParsedFrontmatterLineEntry>,
): ParsedFrontmatter {
  const result: ParsedFrontmatter = {};
  for (const [key, entry] of Object.entries(parsed)) {
    result[key] = entry.value;
  }
  return result;
}

function isYamlBlockScalarIndicator(value: string): boolean {
  return /^[|>][+-]?(\d+)?[+-]?$/.test(value);
}

function shouldPreferInlineLineValue(params: {
  lineEntry: ParsedFrontmatterLineEntry;
  yamlValue: ParsedYamlValue;
}): boolean {
  const { lineEntry, yamlValue } = params;
  if (yamlValue.kind !== "structured") {
    return false;
  }
  if (lineEntry.kind !== "inline") {
    return false;
  }
  if (isYamlBlockScalarIndicator(lineEntry.rawInline)) {
    return false;
  }
  return lineEntry.value.includes(":");
}

function extractFrontmatterBlock(content: string): string | undefined {
  const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
  if (!normalized.startsWith("---")) {
    return undefined;
  }
  const endIndex = normalized.indexOf("\n---", 3);
  if (endIndex === -1) {
    return undefined;
  }
  return normalized.slice(4, endIndex);
}

export function parseFrontmatterBlock(content: string): ParsedFrontmatter {
  const block = extractFrontmatterBlock(content);
  if (!block) {
    return {};
  }

  const lineParsed = parseLineFrontmatter(block);
  const yamlParsed = parseYamlFrontmatter(block);
  if (yamlParsed === null) {
    return lineFrontmatterToPlain(lineParsed);
  }

  const merged: ParsedFrontmatter = {};
  for (const [key, yamlValue] of Object.entries(yamlParsed)) {
    merged[key] = yamlValue.value;
    const lineEntry = lineParsed[key];
    if (!lineEntry) {
      continue;
    }
    if (shouldPreferInlineLineValue({ lineEntry, yamlValue })) {
      merged[key] = lineEntry.value;
    }
  }

  for (const [key, lineEntry] of Object.entries(lineParsed)) {
    if (!(key in merged)) {
      merged[key] = lineEntry.value;
    }
  }

  return merged;
}
