/**
 * As regras de tributação de um "perfil fiscal": o que se repete em toda nota
 * de um tipo de produto ou serviço.
 *
 * O dono da gráfica não sabe o que é CSOSN, e não precisa saber para emitir.
 * Ele escolhe um modelo com nome de gente ("Produção própria — Simples
 * Nacional"), confere com o contador quando puder, e cada produto aponta para
 * um perfil. A nota monta o resto. Nada aqui afirma qual tributação é a certa
 * para a empresa: os modelos são sugestões marcadas como tal, e todo campo pode
 * ser editado.
 */

import { CSOSN as CSOSN_DA_TABELA, CST_ICMS as CST_ICMS_DA_TABELA, UFS } from "./tabelas.ts";

export type Regime = "simplesNacional" | "simplesNacionalExcessoSublimite" | "regimeNormal" | "simplesNacionalMEI";

export const REGIMES: Array<{ valor: Regime; rotulo: string; descricao: string }> = [
  { valor: "simplesNacional", rotulo: "Simples Nacional", descricao: "Micro e pequena empresa optante pelo Simples." },
  { valor: "simplesNacionalMEI", rotulo: "MEI", descricao: "Microempreendedor individual." },
  {
    valor: "simplesNacionalExcessoSublimite",
    rotulo: "Simples Nacional — excesso de sublimite",
    descricao: "Optante do Simples que passou do sublimite do ICMS/ISS do estado.",
  },
  { valor: "regimeNormal", rotulo: "Lucro Presumido ou Real", descricao: "Empresa fora do Simples (regime normal)." },
];

export function regimeUsaCsosn(regime: Regime) {
  return regime !== "regimeNormal";
}

export type RegrasDeMercadoria = {
  natureza: string;
  cfopInterno: number | null;
  cfopInterestadual: number | null;
  /* Venda para consumidor final não contribuinte em outro estado (6107/6108). */
  cfopInterestadualConsumidor: number | null;
  origem: number;
  csosn: number | null;
  cstIcms: number | null;
  aliquotaIcms: number | null;
  reducaoBaseIcms: number | null;
  aliquotaCreditoSimples: number | null;
  pisCst: number | null;
  pisAliquota: number | null;
  cofinsCst: number | null;
  cofinsAliquota: number | null;
  ipiCst: number | null;
  ipiAliquota: number | null;
  ipiEnquadramento: string | null;
  ibsCbsCst: number | null;
  ibsCbsClassificacao: number | null;
  /**
   * Venda para consumidor final de outro estado (DIFAL): a alíquota interna do
   * estado de DESTINO e o adicional do Fundo de Combate à Pobreza dele, por UF.
   * Cada estado tem a sua e elas mudam por lei estadual, então quem informa é o
   * contador — o sistema não inventa alíquota de imposto.
   */
  aliquotaInternaPorUf: Record<string, number>;
  fcpPorUf: Record<string, number>;
  informacoesAdicionais: string | null;
};

export type IssRetido = "nunca" | "sempre" | "cliente_pj";

export type TipoDeTributacaoIss =
  | "taxationInMunicipality"
  | "taxationOutsideMunicipality"
  | "exemption"
  | "immune"
  | "suspendedByCourt"
  | "suspendedByAdministrativeProcedure"
  | "exportation"
  | "nonIncidence";

export type LocalDeIncidencia = "companyMunicipality" | "customerMunicipality" | "serviceProvisionMunicipality";

export type RegrasDeServico = {
  codigoLc116: string | null;
  codigoMunicipal: string | null;
  cnae: string | null;
  nbs: string | null;
  codigoTributacaoNacional: string | null;
  aliquotaIss: number | null;
  issRetido: IssRetido;
  tipoTributacao: TipoDeTributacaoIss;
  localIncidencia: LocalDeIncidencia | null;
  cstPisCofins: string | null;
  anexoSimples: "I" | "II" | "III" | "IV" | "V" | null;
  ibsCbsCst: number | null;
  ibsCbsClassificacao: number | null;
  /* Onde o IBS/CBS é devido (cIndOp), seis números. Vazio: o serviço de notas decide. */
  ibsCbsIndicadorOperacao: string | null;
  informacoesAdicionais: string | null;
};

const numeroOuNulo = (valor: unknown): number | null => {
  if (valor === null || valor === undefined || valor === "") return null;
  const n = typeof valor === "string" ? Number(valor.replace(",", ".")) : Number(valor);
  return Number.isFinite(n) ? n : null;
};
const inteiroOuNulo = (valor: unknown): number | null => {
  const n = numeroOuNulo(valor);
  return n === null ? null : Math.trunc(n);
};
/* A classificação do IBS/CBS começa em 000001: zero não existe na tabela e a
   nota volta recusada. Chegava zero pela tela, que engolia o que se digitava
   depois dos zeros da frente. Zero guardado vale como vazio (o padrão). */
const classificacaoOuNulo = (valor: unknown): number | null => inteiroOuNulo(valor) || null;
const textoOuNulo = (valor: unknown): string | null => {
  const t = typeof valor === "string" ? valor.trim() : "";
  return t ? t : null;
};
const soDigitos = (valor: unknown) => (typeof valor === "string" || typeof valor === "number" ? String(valor).replace(/\D/g, "") : "");

/**
 * Alíquotas do IBS e da CBS no período de teste da reforma tributária (2026):
 * 0,1% de IBS e 0,9% de CBS, conforme a LC 214/2025. A nota não recolhe nada
 * neste ano, mas a SEFAZ exige o grupo com base, alíquota e valor — sem ele a
 * recusa é "Grupo IBS/CBS não informado". Quando o cronograma virar, é aqui que
 * as alíquotas mudam.
 */
export const ALIQUOTAS_DA_REFORMA = { ibsUf: 0.1, ibsMunicipio: 0, cbs: 0.9 };

export const SEM_REGRAS_DE_MERCADORIA: RegrasDeMercadoria = {
  natureza: "Venda de mercadoria",
  cfopInterno: null,
  cfopInterestadual: null,
  cfopInterestadualConsumidor: null,
  origem: 0,
  csosn: null,
  cstIcms: null,
  aliquotaIcms: null,
  reducaoBaseIcms: null,
  aliquotaCreditoSimples: null,
  pisCst: null,
  pisAliquota: null,
  cofinsCst: null,
  cofinsAliquota: null,
  ipiCst: null,
  ipiAliquota: null,
  ipiEnquadramento: null,
  ibsCbsCst: null,
  ibsCbsClassificacao: null,
  aliquotaInternaPorUf: {},
  fcpPorUf: {},
  informacoesAdicionais: null,
};

/** { "SP": 18, "RJ": 20 }: só UF conhecida e alíquota entre 0 e 100. */
function lerAliquotasPorUf(bruto: unknown): Record<string, number> {
  if (!bruto || typeof bruto !== "object" || Array.isArray(bruto)) return {};
  const limpo: Record<string, number> = {};
  for (const [uf, valor] of Object.entries(bruto as Record<string, unknown>)) {
    const sigla = String(uf).toUpperCase().trim();
    const numero = numeroOuNulo(valor);
    if (!UFS.includes(sigla) || numero === null || numero < 0 || numero > 100) continue;
    limpo[sigla] = numero;
  }
  return limpo;
}

export function lerRegrasDeMercadoria(bruto: unknown): RegrasDeMercadoria {
  const r = bruto && typeof bruto === "object" && !Array.isArray(bruto) ? (bruto as Record<string, unknown>) : {};
  const origem = inteiroOuNulo(r.origem);
  return {
    natureza: textoOuNulo(r.natureza) ?? SEM_REGRAS_DE_MERCADORIA.natureza,
    cfopInterno: inteiroOuNulo(r.cfopInterno),
    cfopInterestadual: inteiroOuNulo(r.cfopInterestadual),
    cfopInterestadualConsumidor: inteiroOuNulo(r.cfopInterestadualConsumidor),
    origem: origem !== null && origem >= 0 && origem <= 8 ? origem : 0,
    csosn: inteiroOuNulo(r.csosn),
    cstIcms: inteiroOuNulo(r.cstIcms),
    aliquotaIcms: numeroOuNulo(r.aliquotaIcms),
    reducaoBaseIcms: numeroOuNulo(r.reducaoBaseIcms),
    aliquotaCreditoSimples: numeroOuNulo(r.aliquotaCreditoSimples),
    pisCst: inteiroOuNulo(r.pisCst),
    pisAliquota: numeroOuNulo(r.pisAliquota),
    cofinsCst: inteiroOuNulo(r.cofinsCst),
    cofinsAliquota: numeroOuNulo(r.cofinsAliquota),
    ipiCst: inteiroOuNulo(r.ipiCst),
    ipiAliquota: numeroOuNulo(r.ipiAliquota),
    ipiEnquadramento: textoOuNulo(r.ipiEnquadramento),
    ibsCbsCst: inteiroOuNulo(r.ibsCbsCst),
    ibsCbsClassificacao: classificacaoOuNulo(r.ibsCbsClassificacao),
    aliquotaInternaPorUf: lerAliquotasPorUf(r.aliquotaInternaPorUf),
    fcpPorUf: lerAliquotasPorUf(r.fcpPorUf),
    informacoesAdicionais: textoOuNulo(r.informacoesAdicionais),
  };
}

export function lerRegrasDeServico(bruto: unknown): RegrasDeServico {
  const r = bruto && typeof bruto === "object" && !Array.isArray(bruto) ? (bruto as Record<string, unknown>) : {};
  const retido = String(r.issRetido ?? "nunca");
  const tipo = String(r.tipoTributacao ?? "taxationInMunicipality") as TipoDeTributacaoIss;
  const local = textoOuNulo(r.localIncidencia) as LocalDeIncidencia | null;
  const anexo = textoOuNulo(r.anexoSimples)?.toUpperCase() as RegrasDeServico["anexoSimples"];
  return {
    codigoLc116: textoOuNulo(r.codigoLc116),
    codigoMunicipal: textoOuNulo(r.codigoMunicipal),
    cnae: soDigitos(r.cnae) || null,
    nbs: textoOuNulo(r.nbs),
    codigoTributacaoNacional: textoOuNulo(r.codigoTributacaoNacional),
    aliquotaIss: numeroOuNulo(r.aliquotaIss),
    issRetido: retido === "sempre" || retido === "cliente_pj" ? retido : "nunca",
    tipoTributacao: TIPOS_DE_TRIBUTACAO_ISS.some((t) => t.valor === tipo) ? tipo : "taxationInMunicipality",
    localIncidencia: local && ["companyMunicipality", "customerMunicipality", "serviceProvisionMunicipality"].includes(local) ? local : null,
    cstPisCofins: textoOuNulo(r.cstPisCofins),
    anexoSimples: anexo && ["I", "II", "III", "IV", "V"].includes(anexo) ? anexo : null,
    ibsCbsCst: inteiroOuNulo(r.ibsCbsCst),
    ibsCbsClassificacao: classificacaoOuNulo(r.ibsCbsClassificacao),
    ibsCbsIndicadorOperacao: soDigitos(r.ibsCbsIndicadorOperacao).length === 6 ? soDigitos(r.ibsCbsIndicadorOperacao) : null,
    informacoesAdicionais: textoOuNulo(r.informacoesAdicionais),
  };
}

/* ---------------- Tabelas com nome de gente ---------------- */

export const ORIGENS: Array<{ codigo: number; nome: string }> = [
  { codigo: 0, nome: "Nacional" },
  { codigo: 1, nome: "Estrangeira — importação direta" },
  { codigo: 2, nome: "Estrangeira — comprada no mercado interno" },
  { codigo: 3, nome: "Nacional com mais de 40% de conteúdo importado" },
  { codigo: 4, nome: "Nacional produzida por processo produtivo básico" },
  { codigo: 5, nome: "Nacional com até 40% de conteúdo importado" },
  { codigo: 6, nome: "Estrangeira — importação direta, sem similar nacional" },
  { codigo: 7, nome: "Estrangeira — mercado interno, sem similar nacional" },
  { codigo: 8, nome: "Nacional com mais de 70% de conteúdo importado" },
];

/* A lista completa mora em tabelas.ts, junto com CFOP, serviços e os links
   oficiais. Aqui ela só ganha o formato que os campos de escolha usam — duas
   listas diferentes para a mesma coisa acabariam divergindo. */
export const CSOSN: Array<{ codigo: number; nome: string }> = CSOSN_DA_TABELA.map((item) => ({
  codigo: Number(item.codigo),
  nome: item.descricao,
}));

export const CST_ICMS: Array<{ codigo: number; nome: string }> = CST_ICMS_DA_TABELA.map((item) => ({
  codigo: Number(item.codigo),
  nome: `${item.codigo} — ${item.descricao}`,
}));

export const CST_PIS_COFINS: Array<{ codigo: number; nome: string; comAliquota: boolean }> = [
  { codigo: 1, nome: "01 — Tributável, alíquota básica", comAliquota: true },
  { codigo: 2, nome: "02 — Tributável, alíquota diferenciada", comAliquota: true },
  { codigo: 4, nome: "04 — Monofásica, revenda a alíquota zero", comAliquota: false },
  { codigo: 5, nome: "05 — Substituição tributária", comAliquota: false },
  { codigo: 6, nome: "06 — Alíquota zero", comAliquota: false },
  { codigo: 7, nome: "07 — Isenta", comAliquota: false },
  { codigo: 8, nome: "08 — Sem incidência", comAliquota: false },
  { codigo: 9, nome: "09 — Com suspensão", comAliquota: false },
  { codigo: 49, nome: "49 — Outras operações de saída", comAliquota: false },
  { codigo: 99, nome: "99 — Outras operações", comAliquota: false },
];

export const TIPOS_DE_TRIBUTACAO_ISS: Array<{ valor: TipoDeTributacaoIss; nome: string }> = [
  { valor: "taxationInMunicipality", nome: "Tributado no município" },
  { valor: "taxationOutsideMunicipality", nome: "Tributado fora do município" },
  { valor: "exemption", nome: "Isento" },
  { valor: "immune", nome: "Imune" },
  { valor: "nonIncidence", nome: "Não incidência" },
  { valor: "exportation", nome: "Exportação de serviço" },
  { valor: "suspendedByCourt", nome: "Suspenso por decisão judicial" },
  { valor: "suspendedByAdministrativeProcedure", nome: "Suspenso por processo administrativo" },
];

export const TEXTO_DO_SIMPLES =
  "Documento emitido por ME ou EPP optante pelo Simples Nacional. Não gera direito a crédito fiscal de IPI.";

/* ---------------- Modelos prontos (sugestões) ---------------- */

export type ModeloDePerfil<T> = {
  chave: string;
  nome: string;
  descricao: string;
  regimes: Regime[];
  regras: T;
};

const SIMPLES_E_MEI: Regime[] = ["simplesNacional", "simplesNacionalMEI", "simplesNacionalExcessoSublimite"];

export const MODELOS_DE_MERCADORIA: ModeloDePerfil<RegrasDeMercadoria>[] = [
  {
    chave: "producao-simples",
    nome: "Produção própria",
    descricao: "O que a empresa fabrica ou personaliza e vende como produto pronto.",
    regimes: SIMPLES_E_MEI,
    regras: {
      ...SEM_REGRAS_DE_MERCADORIA,
      natureza: "Venda de produção do estabelecimento",
      cfopInterno: 5101,
      cfopInterestadual: 6101,
      cfopInterestadualConsumidor: 6107,
      csosn: 102,
      pisCst: 49,
      cofinsCst: 49,
      informacoesAdicionais: TEXTO_DO_SIMPLES,
    },
  },
  {
    chave: "revenda-simples",
    nome: "Revenda de mercadoria",
    descricao: "Produto comprado pronto de fornecedor e revendido sem transformação.",
    regimes: SIMPLES_E_MEI,
    regras: {
      ...SEM_REGRAS_DE_MERCADORIA,
      natureza: "Venda de mercadoria",
      cfopInterno: 5102,
      cfopInterestadual: 6102,
      cfopInterestadualConsumidor: 6108,
      csosn: 102,
      pisCst: 49,
      cofinsCst: 49,
      informacoesAdicionais: TEXTO_DO_SIMPLES,
    },
  },
  {
    chave: "producao-normal",
    nome: "Produção própria",
    descricao: "Regime normal. Informe a alíquota de ICMS do seu estado e as de PIS/COFINS do seu enquadramento.",
    regimes: ["regimeNormal"],
    regras: {
      ...SEM_REGRAS_DE_MERCADORIA,
      natureza: "Venda de produção do estabelecimento",
      cfopInterno: 5101,
      cfopInterestadual: 6101,
      cfopInterestadualConsumidor: 6107,
      cstIcms: 0,
      pisCst: 1,
      cofinsCst: 1,
      ibsCbsCst: 0,
      ibsCbsClassificacao: 1,
    },
  },
  {
    chave: "revenda-normal",
    nome: "Revenda de mercadoria",
    descricao: "Regime normal. Informe a alíquota de ICMS do seu estado e as de PIS/COFINS do seu enquadramento.",
    regimes: ["regimeNormal"],
    regras: {
      ...SEM_REGRAS_DE_MERCADORIA,
      natureza: "Venda de mercadoria",
      cfopInterno: 5102,
      cfopInterestadual: 6102,
      cfopInterestadualConsumidor: 6108,
      cstIcms: 0,
      pisCst: 1,
      cofinsCst: 1,
      ibsCbsCst: 0,
      ibsCbsClassificacao: 1,
    },
  },
];

export const SEM_REGRAS_DE_SERVICO: RegrasDeServico = {
  codigoLc116: null,
  codigoMunicipal: null,
  cnae: null,
  nbs: null,
  codigoTributacaoNacional: null,
  aliquotaIss: null,
  issRetido: "nunca",
  tipoTributacao: "taxationInMunicipality",
  localIncidencia: null,
  cstPisCofins: null,
  anexoSimples: null,
  ibsCbsCst: null,
  ibsCbsClassificacao: null,
  ibsCbsIndicadorOperacao: null,
  informacoesAdicionais: null,
};

export const MODELOS_DE_SERVICO: ModeloDePerfil<RegrasDeServico>[] = [
  {
    chave: "grafico-sob-encomenda",
    nome: "Serviço gráfico sob encomenda",
    descricao: "Impressão personalizada feita para o cliente final (item 13.05 da lista de serviços).",
    regimes: ["simplesNacional", "simplesNacionalMEI", "simplesNacionalExcessoSublimite", "regimeNormal"],
    regras: { ...SEM_REGRAS_DE_SERVICO, codigoLc116: "13.05" },
  },
  {
    chave: "comunicacao-visual",
    nome: "Criação de arte e comunicação visual",
    descricao: "Arte, design e comunicação visual (item 23.01 da lista de serviços).",
    regimes: ["simplesNacional", "simplesNacionalMEI", "simplesNacionalExcessoSublimite", "regimeNormal"],
    regras: { ...SEM_REGRAS_DE_SERVICO, codigoLc116: "23.01" },
  },
  {
    chave: "outro-servico",
    nome: "Outro serviço",
    descricao: "Preencha o código do serviço com a ajuda do seu contador.",
    regimes: ["simplesNacional", "simplesNacionalMEI", "simplesNacionalExcessoSublimite", "regimeNormal"],
    regras: { ...SEM_REGRAS_DE_SERVICO },
  },
];

/** O que falta num perfil de mercadoria para emitir. */
export function faltasDaMercadoria(regras: RegrasDeMercadoria, regime: Regime): string[] {
  const faltas: string[] = [];
  if (!regras.cfopInterno || String(regras.cfopInterno).length !== 4) faltas.push("CFOP para dentro do estado");
  if (!regras.cfopInterestadual || String(regras.cfopInterestadual).length !== 4) faltas.push("CFOP para outro estado");
  if (regimeUsaCsosn(regime)) {
    if (!regras.csosn || !CSOSN.some((c) => c.codigo === regras.csosn)) faltas.push("CSOSN");
  } else {
    if (regras.cstIcms === null || !CST_ICMS.some((c) => c.codigo === regras.cstIcms)) faltas.push("CST do ICMS");
    if ((regras.cstIcms === 0 || regras.cstIcms === 20) && !(Number(regras.aliquotaIcms) > 0)) faltas.push("alíquota do ICMS");
  }
  if (regras.pisCst === null) faltas.push("CST do PIS");
  if (regras.cofinsCst === null) faltas.push("CST do COFINS");
  const pisComAliquota = CST_PIS_COFINS.find((c) => c.codigo === regras.pisCst)?.comAliquota;
  const cofinsComAliquota = CST_PIS_COFINS.find((c) => c.codigo === regras.cofinsCst)?.comAliquota;
  if (pisComAliquota && !(Number(regras.pisAliquota) > 0)) faltas.push("alíquota do PIS");
  if (cofinsComAliquota && !(Number(regras.cofinsAliquota) > 0)) faltas.push("alíquota do COFINS");
  return faltas;
}

export type ExigenciasDaCidade = {
  codigoLc116?: boolean;
  codigoMunicipal?: boolean;
  /* A cidade aceita o código, mas só quem tem preenche. */
  codigoMunicipalOpcional?: boolean;
  cnae?: boolean;
  nbs?: boolean;
  codigoTributacaoNacional?: boolean;
  anexoSimples?: boolean;
};

/** O que falta num perfil de serviço, dado o que a prefeitura exige. */
export function faltasDoServico(regras: RegrasDeServico, exige: ExigenciasDaCidade): string[] {
  const faltas: string[] = [];
  if (regras.aliquotaIss === null || regras.aliquotaIss < 0) faltas.push("alíquota do ISS");
  if ((exige.codigoLc116 ?? true) && !regras.codigoLc116) faltas.push("código do serviço (LC 116)");
  if (exige.codigoMunicipal && !regras.codigoMunicipal) faltas.push("código do serviço no município");
  if (exige.cnae && !regras.cnae) faltas.push("CNAE");
  if (exige.nbs && !regras.nbs) faltas.push("código NBS");
  if (exige.codigoTributacaoNacional && !regras.codigoTributacaoNacional) faltas.push("código de tributação nacional");
  if (exige.anexoSimples && !regras.anexoSimples) faltas.push("anexo do Simples Nacional");
  return faltas;
}
