/**
 * Swift accessor generator.
 *
 * Emits a compile-time-checked stand-in for each localization key so Apple call
 * sites stop passing raw string literals. The runtime path is unchanged: plain
 * keys become `String.LocalizationValue` constants used with `String(localized:)`
 * exactly as before, and Apple's catalog lookup still does 100% of the work.
 *
 * Keys group by their first `_`-delimited segment (`button_…` -> `enum Button`);
 * the remainder becomes a camelCase member. Plain keys are `static let`
 * constants; keys with args become `static func` with typed, named parameters
 * derived from the entry's `args` metadata.
 */
import { targetsOf, type Arg, type StringEntry, type StringsFile } from "../types";

const HEADER = `// Generated by localization/ (bun run src/cli.ts generate). Do not edit.
// Keys resolve through Apple's String Catalog exactly as a literal would; these
// accessors only make the key compile-time-checked. If a runtime language
// switcher (live \`.environment(\\.locale)\`) is ever added, switch the plain
// \`static let\` constants to computed \`static var\` so the locale is not frozen.
import Foundation
`;

/** Swift type for a localization arg. */
function swiftType(arg: Arg): string {
  switch (arg.type) {
    case "int":
      return "Int";
    case "double":
      return "Double";
    case "string":
      return "String";
  }
}

/** `create_new_transfer` -> `createNewTransfer`. */
function camel(segment: string): string {
  const parts = segment.split("_").filter(Boolean);
  return parts
    .map((p, i) => (i === 0 ? p : p.charAt(0).toUpperCase() + p.slice(1)))
    .join("");
}

/** `button` -> `Button`. */
function pascal(segment: string): string {
  const c = camel(segment);
  return c.charAt(0).toUpperCase() + c.slice(1);
}

/** A key is groupable only when it starts with an identifier-safe segment. */
function isGroupableKey(key: string): boolean {
  return /^[A-Za-z][A-Za-z0-9_]*$/.test(key);
}

/** Collapse whitespace/newlines so a value fits on one doc-comment line. */
function oneLine(text: string): string {
  return text.replace(/\s*\n\s*/g, " ").trim();
}

/** The displayable text for a language (plural shows its `other` form). */
function translationText(entry: StringEntry, lang: string): string | undefined {
  return entry.translations?.[lang] ?? entry.plural?.[lang]?.other;
}

/**
 * A rich Quick Help block: source-language text as the abstract, then the raw
 * key, the context note, and every translation. Quick Help renders the Markdown.
 */
function docComment(key: string, entry: StringEntry, doc: StringsFile): string {
  const lines: string[] = [];
  const source = translationText(entry, doc.sourceLanguage);
  if (source) lines.push(oneLine(source), "");

  lines.push(`Key: \`${key}\``);
  if (entry.context) lines.push(`Context: ${oneLine(entry.context)}`);
  lines.push("");

  for (const lang of doc.supportedLanguages) {
    const value = translationText(entry, lang);
    if (value !== undefined) lines.push(`- ${lang}: ${oneLine(value)}`);
  }

  return lines
    .map((line) => (line ? `        /// ${line}` : "        ///"))
    .join("\n");
}

function memberFor(
  key: string,
  member: string,
  entry: StringEntry,
  doc: StringsFile,
): string {
  const comment = docComment(key, entry, doc);
  const args = entry.args ?? [];

  // Plain key: a #define-style constant. Apple resolves it via String(localized:).
  if (args.length === 0 && !entry.plural) {
    return `${comment}\n        static let ${member}: String.LocalizationValue = "${key}"`;
  }

  // Arg'd (or plural) key: a typed, named function that applies the arguments
  // through the same String(format: String(localized:)) path used before.
  const params = args.map((a) => `${a.name}: ${swiftType(a)}`).join(", ");
  const callArgs = args.map((a) => a.name).join(", ");
  // The positional format string lives in the catalog; look it up by key and
  // apply the args exactly as the hand-written call sites did.
  return `${comment}\n        static func ${member}(${params}) -> String {\n            String(format: String(localized: "${key}"), ${callArgs})\n        }`;
}

export function renderSwiftAccessors(doc: StringsFile): string {
  // group segment -> rendered members
  const groups = new Map<string, string[]>();

  for (const [key, entry] of Object.entries(doc.strings)) {
    if (!targetsOf(entry).includes("apple")) continue;
    if (!isGroupableKey(key)) continue; // skips legacy `%@` literal keys

    const underscore = key.indexOf("_");
    const groupSeg = underscore === -1 ? key : key.slice(0, underscore);
    const memberSeg = underscore === -1 ? key : key.slice(underscore + 1);
    const group = pascal(groupSeg);
    const member = camel(memberSeg) || camel(groupSeg);

    const list = groups.get(group) ?? [];
    list.push(memberFor(key, member, entry, doc));
    groups.set(group, list);
  }

  const body = [...groups.keys()]
    .sort()
    .map((group) => {
      const members = groups.get(group)!.join("\n");
      return `    enum ${group} {\n${members}\n    }`;
    })
    .join("\n");

  return `${HEADER}\nenum L10n {\n${body}\n}\n`;
}
