<template>
  <v-text-field
    :model-value="displayValue"
    :label="opcoes?.rotulo"
    :placeholder="opcoes?.placeholder"
    :type="isInteiro ? 'number' : 'text'"
    :inputmode="isInteiro ? 'numeric' : 'decimal'"
    :pattern="isInteiro ? '[0-9]*' : '[0-9.,]*'"
    v-bind="attrs"
    @update:model-value="onUpdateModelValue"
    @focus="() => emit('focus')"
    @blur="() => emit('blur')"
  >
    <!--
      Em alguns cenários (ex.: type="number"), o prop `suffix` do Vuetify pode não aparecer.
      Usamos slots para garantir exibição consistente de prefixo/sufixo.
    -->
    <template v-if="opcoes?.prefixo" #prepend-inner>
      <span class="eli-entrada__prefixo">{{ opcoes.prefixo }}</span>
    </template>
    <template v-if="opcoes?.sufixo" #append-inner>
      <span class="eli-entrada__sufixo">{{ opcoes.sufixo }}</span>
    </template>
  </v-text-field>
</template>

<script lang="ts">
import { computed, defineComponent, PropType, ref, watch } from "vue";
import type { PadroesEntradas } from "./tiposEntradas";

type EntradaNumero = PadroesEntradas["numero"];

function casasDecimaisFromPrecisao(precisao: number): number {
  if (!Number.isFinite(precisao) || precisao <= 0) return 0;
  if (precisao >= 1) return 0;

  // Preferência por contar casas decimais na representação (ex.: 0.01 -> 2)
  const texto = precisao.toString();
  if (texto.includes("e-")) {
    const [, exp] = texto.split("e-");
    const n = Number(exp);
    return Number.isFinite(n) ? n : 0;
  }
  const idx = texto.indexOf(".");
  if (idx === -1) return 0;
  const dec = texto.slice(idx + 1).replace(/0+$/, "");
  return dec.length;
}

function parseNumero(texto: string): number | null {
  const normalizado = (texto ?? "").trim().replace(/,/g, ".");
  if (!normalizado) return null;
  const n = Number(normalizado);
  if (Number.isNaN(n)) return null;
  return n;
}

function formatarNumero(value: number | null | undefined, casas: number | null) {
  if (value === null || value === undefined) return "";
  if (casas === null) return String(value);

  // Garantimos que o texto visual corresponda ao value (fixed).
  const fixed = Number(value).toFixed(Math.max(0, casas));
  // Exibe com vírgula (pt-BR)
  return fixed.replace(/\./g, ",");
}

function somenteNumeros(texto: string) {
  return (texto ?? "").replace(/\D+/g, "");
}

function somenteNumerosESeparadorDecimal(texto: string) {
  // Mantém apenas números e UM separador decimal ("," ou "."), sem sinais.
  // Implementação baseada em regex + colapso de separadores.
  const limpo = (texto ?? "").replace(/[^0-9.,]+/g, "");
  const matchSep = limpo.match(/[.,]/);
  if (!matchSep) return limpo;

  const sep = matchSep[0];
  const idx = limpo.indexOf(sep);
  const antesRaw = limpo.slice(0, idx).replace(/[.,]/g, "");
  const depois = limpo.slice(idx + 1).replace(/[.,]/g, "");

  // Se o usuário começa pelo separador (",4"), normalizamos para "0,4".
  const antes = antesRaw.length ? antesRaw : "0";
  return `${antes}${sep}${depois}`;
}

function limitarCasasDecimais(texto: string, casas: number | null) {
  if (casas === null) return texto;
  if (casas <= 0) return texto.replace(/[.,]/g, "");

  const matchSep = texto.match(/[.,]/);
  if (!matchSep) return texto;
  const sep = matchSep[0];
  const idx = texto.indexOf(sep);

  const inteiro = texto.slice(0, idx);
  const frac = texto.slice(idx + 1);
  return `${inteiro}${sep}${frac.slice(0, casas)}`;
}

function valorParcialDecimal(texto: string): number | null {
  // Regra (B): se o usuário digitou "1," ou "1." emite 1.
  const m = texto.match(/^(\d+)[.,]$/);
  if (!m) return null;
  const n = Number(m[1]);
  return Number.isNaN(n) ? null : n;
}

export default defineComponent({
  name: "EliEntradaNumero",
  inheritAttrs: false,
  props: {
    /** Interface padrão (EliEntrada): value + opcoes. */
    value: {
      type: [Number, null] as unknown as PropType<EntradaNumero["value"]>,
      default: undefined,
    },
    opcoes: {
      type: Object as PropType<EntradaNumero["opcoes"]>,
      required: true,
    },
  },
  emits: {
    "update:value": (_v: EntradaNumero["value"]) => true,
    /** Compat Vue2 (v-model padrão: value + input) */
    input: (_v: EntradaNumero["value"]) => true,
    change: (_v: EntradaNumero["value"]) => true,
    focus: () => true,
    blur: () => true,
  },
  setup(props, { attrs, emit }) {
    // Se `precisao` não existir => não limitamos casas (mas ainda bloqueamos caracteres inválidos).
    const casasDecimais = computed<number | null>(() => {
      const p = props.opcoes?.precisao;
      if (p === undefined || p === null) return null;
      return casasDecimaisFromPrecisao(p);
    });

    const isInteiro = computed(() => {
      // quando não existe precisão, tratamos como decimal livre
      return casasDecimais.value === 0;
    });

    const isFixedPoint = computed(() => {
      // Quando precisao existe e é < 1, controlamos a vírgula automaticamente.
      const casas = casasDecimais.value;
      return casas !== null && casas > 0;
    });

    // Controle do texto exibido: impede o campo de ficar com caracteres inválidos.
    const displayValue = ref<string>("");
    const ultimoEmitido = ref<EntradaNumero["value"] | undefined>(undefined);

    watch(
      () => props.value,
      (v) => {
        // Se foi uma atualização resultante do que acabamos de emitir, não sobrescreve.
        // Isso evita o efeito de "apagar" o texto ao digitar algo parcial (ex.: "1,")
        // quando o valor emitido é `1` ou `null`.
        if (v === ultimoEmitido.value) return;

        displayValue.value = formatarNumero(v as any, casasDecimais.value);
        ultimoEmitido.value = v;
      },
      { immediate: true }
    );

    function onUpdateModelValue(texto: string) {
      // Modo fixed-point: usuário digita números continuamente e a vírgula é inserida automaticamente.
      if (isFixedPoint.value) {
        const casas = casasDecimais.value ?? 0;
        const digitos = somenteNumeros(texto);
        // ex.: casas=2, "1" => 0.01 ; "123" => 1.23
        const baseInt = digitos ? Number(digitos) : 0;
        const divisor = Math.pow(10, casas);
        const n = digitos ? baseInt / divisor : null;

        const out = (n === null ? null : n) as EntradaNumero["value"];
        ultimoEmitido.value = out;
        emit("update:value", out);
        emit("input", out);
        emit("change", out);

        // display deve ser sempre o reflexo do value
        displayValue.value = formatarNumero(out as any, casas);
        return;
      }

      // Modo livre (sem precisao) ou inteiro: aceita números e 1 separador (no caso livre)
      const base = isInteiro.value ? somenteNumeros(texto) : somenteNumerosESeparadorDecimal(texto);
      const textoFiltrado = isInteiro.value ? base : limitarCasasDecimais(base, casasDecimais.value);

      // Emissão:
      // - vazio => null
      // - decimal parcial ("1,") => 1 (regra B)
      // - caso geral => parse normal
      let out: EntradaNumero["value"] = null;
      if (textoFiltrado) {
        const parcial = isInteiro.value ? null : valorParcialDecimal(textoFiltrado);
        const n = parcial ?? parseNumero(textoFiltrado);
        out = (n === null ? null : n) as EntradaNumero["value"];
      }

      ultimoEmitido.value = out;

      emit("update:value", out);
      emit("input", out);
      emit("change", out);

      // display deve sempre corresponder ao value final
      displayValue.value = formatarNumero(out as any, casasDecimais.value);
    }

    return { attrs, emit, displayValue, isInteiro, onUpdateModelValue };
  },
});
</script>

<style>
.eli-entrada__prefixo,
.eli-entrada__sufixo {
  opacity: 0.75;
  font-size: 0.9em;
  white-space: nowrap;
}

.eli-entrada__prefixo {
  margin-right: 6px;
}

.eli-entrada__sufixo {
  margin-left: 6px;
}
</style>
