import type { Clarizen, EntityDescription, FieldDescription } from "@arco/clarizen-lib";
import { isCancel, select, text } from "@clack/prompts";
import {
  formatFieldOptionLabel,
  isUpdatableField,
} from "./field-hints.js";

export interface EntityChoice {
  typeName: string;
  label?: string;
}

export interface FieldChoice {
  field: FieldDescription;
  entity: EntityDescription;
}

async function describeEntity(
  clarizen: Clarizen,
  typeName: string,
): Promise<EntityDescription | undefined> {
  const result = await clarizen.metadata.describeMetadata({
    typeNames: [typeName],
    flags: ["fields"],
  });
  return result.entityDescriptions?.find((e) => e.typeName === typeName);
}

export async function listEntityChoices(
  clarizen: Clarizen,
): Promise<EntityChoice[]> {
  const { typeNames } = await clarizen.metadata.listEntities();
  const sorted = [...typeNames].sort((a, b) => a.localeCompare(b, "pt-BR"));
  return sorted.map((typeName) => ({ typeName }));
}

function filterOptions<T extends { value: string; label: string }>(
  allOptions: T[],
  term: string,
): T[] {
  const normalized = term.trim().toLowerCase();
  if (!normalized) return allOptions;

  return allOptions.filter(
    (o) =>
      o.label.toLowerCase().includes(normalized) ||
      o.value.toLowerCase().includes(normalized),
  );
}

async function pickFromList<T extends { value: string; label: string }>(
  message: string,
  options: T[],
): Promise<string | symbol> {
  if (options.length === 0) {
    throw new Error("Nenhuma opção disponível.");
  }

  if (options.length === 1) {
    return options[0]!.value;
  }

  return select({ message, options });
}

export async function pickFromFiltered<T extends { value: string; label: string }>(
  message: string,
  allOptions: T[],
  filterPrompt: string,
): Promise<string | symbol> {
  if (allOptions.length === 0) {
    throw new Error("Nenhuma opção disponível.");
  }

  const mode = await select({
    message: filterPrompt,
    options: [
      { value: "all", label: "Ver lista completa" },
      { value: "filter", label: "Filtrar por texto" },
    ],
  });
  if (isCancel(mode)) return mode;

  let options = allOptions;

  if (mode === "filter") {
    const filter = await text({
      message: "Digite parte do nome para filtrar",
    });
    if (isCancel(filter)) return filter;

    options = filterOptions(allOptions, filter);
    if (options.length === 0) {
      console.log("Nenhum resultado para esse filtro. Tente outro termo.\n");
      return pickFromFiltered(message, allOptions, filterPrompt);
    }
  }

  return pickFromList(message, options);
}

export async function pickEntityType(
  clarizen: Clarizen,
): Promise<EntityChoice | symbol> {
  const choices = await listEntityChoices(clarizen);
  const options = choices.map((c) => ({
    value: c.typeName,
    label: c.typeName,
  }));

  const selected = await pickFromFiltered(
    "Escolha o tipo de entidade (nível)",
    options,
    "Filtrar tipos de entidade",
  );

  if (isCancel(selected)) return selected;

  const entity = await describeEntity(clarizen, selected);
  return {
    typeName: selected,
    label: entity?.label,
  };
}

export async function getUpdatableFields(
  clarizen: Clarizen,
  typeName: string,
): Promise<FieldChoice[]> {
  const entity = await describeEntity(clarizen, typeName);
  if (!entity) {
    throw new Error(`Metadata não encontrada para ${typeName}.`);
  }

  const fields = (entity.fields ?? [])
    .filter(isUpdatableField)
    .sort((a, b) => {
      const labelA = a.label ?? a.name ?? "";
      const labelB = b.label ?? b.name ?? "";
      return labelA.localeCompare(labelB, "pt-BR");
    });

  return fields.map((field) => ({ field, entity }));
}

export async function pickField(
  clarizen: Clarizen,
  typeName: string,
): Promise<FieldChoice | symbol> {
  const fieldChoices = await getUpdatableFields(clarizen, typeName);

  if (fieldChoices.length === 0) {
    throw new Error(`Nenhum campo atualizável encontrado para ${typeName}.`);
  }

  const options = fieldChoices.map(({ field }) => ({
    value: field.name!,
    label: formatFieldOptionLabel(field),
  }));

  const selected = await pickFromFiltered(
    "Escolha o campo a atualizar",
    options,
    "Filtrar campos",
  );

  if (isCancel(selected)) return selected;

  const match = fieldChoices.find((c) => c.field.name === selected);
  if (!match) {
    throw new Error(`Campo não encontrado: ${selected}`);
  }

  return match;
}

export async function getEntityMetadata(
  clarizen: Clarizen,
  typeName: string,
): Promise<EntityDescription> {
  const entity = await describeEntity(clarizen, typeName);
  if (!entity) {
    throw new Error(`Metadata não encontrada para ${typeName}.`);
  }
  return entity;
}
