// ============================================================
// MAPA DA FAZENDA (app de campo) — seção do "Consultar".
// Substitui a foto do quadro branco: o mesmo mapa georreferenciado da versão
// web, colorido por situação do piquete/módulo, com toque → histórico.
//
// DECISÕES DE DESENHO
// • Uma regra de cor por vez. O usuário escolhe a LENTE (Situação | Descanso);
//   nunca duas escalas competindo no mesmo mapa.
// • O piquete é a unidade colorida; o MÓDULO é o contorno grosso + o pin com
//   nome e nº de cabeças. Assim "onde está o gado" se lê sem zoom.
// • Cabeças aparecem como número dentro do piquete ocupado (com halo branco),
//   porque é o dado que eles anotam no quadro.
// • Toque no piquete → ficha (status atual) → histórico do piquete.
//   Toque no pin do módulo → ficha do módulo → abrir módulo / histórico.
// ============================================================
const MAPA_DS = () => window.FazendaGuanabaraDesignSystem_b62f7e
const MAPA_P = () => window.PAST

// ---- Escalas de cor ------------------------------------------------------
// Situação: 4 estados categóricos (o vocabulário que eles já usam).
const SIT = {
  lote:         { fill: '#2b6b32', stroke: '#1d4a22', label: 'Com lote',     fg: '#fff' },
  descanso:     { fill: '#f6f7f2', stroke: '#ccd3c5', label: 'Em descanso',  fg: '#33422b' },
  indisponivel: { fill: '#f6dcaf', stroke: '#dfbe88', label: 'Indisponível', fg: '#6b5a35' },
}
// Descanso: rampa sequencial palha → verde (mais verde = mais descansado).
const RAMP = [
  { max: 10,       fill: '#f0e8cf', stroke: '#c9bd93', label: 'até 10' },
  { max: 20,       fill: '#dbe2ab', stroke: '#adb87a', label: '11–20' },
  { max: 30,       fill: '#aecd8c', stroke: '#7fa063', label: '21–30' },
  { max: Infinity, fill: '#7cae6f', stroke: '#4f7a49', label: '31+' },
]
const rampDe = (d) => RAMP.find((r) => d <= r.max)
// ---- Cor do MÓDULO: canal de IDENTIDADE ---------------------------------
// Mesmo esquema do mapa web: a cor do módulo (definida no cadastro) pinta a área
// em tinta clara e fecha o bloco com contorno saturado; a SITUAÇÃO do piquete
// continua no preenchimento (com lote / sem lote / indisponível). Verde e âmbar
// ficam fora da paleta porque já são estados.
const MOD_CORES = ['#9163cb', '#46a7d0', '#da7c48', '#f33a3a', '#c2557f', '#6f7fd0', '#2f8f8f', '#a05a2c', '#8a8f3c']
const modDark = (hex, f = 0.68) => {
  const n = parseInt(hex.slice(1), 16)
  const r = Math.round(((n >> 16) & 255) * f), g = Math.round(((n >> 8) & 255) * f), b = Math.round((n & 255) * f)
  return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
}

// Geometria da malha: pontos, bbox e índice espacial de cada polígono. Serve
// para descobrir quem está de cada lado de uma divisa.
function mapaGeom(polys) {
  const G = 60, pts = {}, bb = {}, buckets = new Map()
  polys.forEach((p) => {
    const nums = (p.d.match(/-?\d+(?:\.\d+)?/g) || []).map(Number)
    const arr = []
    for (let i = 0; i + 1 < nums.length; i += 2) arr.push([nums[i], nums[i + 1]])
    if (arr.length < 3) return
    pts[p.id] = arr
    let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9
    arr.forEach(([x, y]) => { if (x < x0) x0 = x; if (x > x1) x1 = x; if (y < y0) y0 = y; if (y > y1) y1 = y })
    bb[p.id] = { x0, y0, x1, y1 }
    for (let gx = Math.floor(x0 / G); gx <= Math.floor(x1 / G); gx++) for (let gy = Math.floor(y0 / G); gy <= Math.floor(y1 / G); gy++) {
      const k = gx + ':' + gy, a = buckets.get(k); if (a) a.push(p.id); else buckets.set(k, [p.id])
    }
  })
  const dentro = (arr, x, y) => { let c = false
    for (let i = 0, j = arr.length - 1; i < arr.length; j = i++) {
      const xi = arr[i][0], yi = arr[i][1], xj = arr[j][0], yj = arr[j][1]
      if (((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) c = !c
    } return c }
  const perto = (x, y) => { const gx = Math.floor(x / G), gy = Math.floor(y / G), out = []
    for (let i = -1; i <= 1; i++) for (let j = -1; j <= 1; j++) { const a = buckets.get((gx + i) + ':' + (gy + j)); if (a) out.push(...a) }
    return out }
  // dono do ponto; vãos finos entre polígonos "colam" no mais próximo, para não
  // sobrar traço grosso solto no meio de um bloco.
  const celulaEm = (x, y) => {
    const cand = perto(x, y)
    for (const id of cand) { const b = bb[id]
      if (!b || x < b.x0 - 0.2 || x > b.x1 + 0.2 || y < b.y0 - 0.2 || y > b.y1 + 0.2) continue
      if (dentro(pts[id], x, y)) return id }
    let best = null, bd = 20
    for (const id of cand) for (const q of pts[id]) { const d = (q[0] - x) ** 2 + (q[1] - y) ** 2; if (d < bd) { bd = d; best = id } }
    return best
  }
  // contorno por LADO: desenha a divisa só quando do outro lado há outro grupo
  const contornos = (grupoDe) => {
    const byG = {}
    polys.forEach((p) => {
      const g = grupoDe(p.id); if (g == null || !pts[p.id]) return
      const arr = pts[p.id], segs = byG[g] || (byG[g] = [])
      for (let i = 0; i < arr.length; i++) {
        const a = arr[i], b = arr[(i + 1) % arr.length]
        const dx = b[0] - a[0], dy = b[1] - a[1], len = Math.hypot(dx, dy)
        if (len < 0.6) continue
        const mx = (a[0] + b[0]) / 2, my = (a[1] + b[1]) / 2
        const nx = -dy / len, ny = dx / len
        const sgn = ((p.cx - mx) * nx + (p.cy - my) * ny) > 0 ? -1 : 1
        const viz = celulaEm(mx + nx * 2.2 * sgn, my + ny * 2.2 * sgn)
        if (viz == null || grupoDe(viz) !== g) segs.push('M' + a[0] + ',' + a[1] + 'L' + b[0] + ',' + b[1])
      }
    })
    return Object.entries(byG).map(([g, segs]) => ({ g, d: segs.join('') }))
  }
  return { pts, bb, celulaEm, contornos }
}

// terreno de contexto (fora do cadastro / módulo arquivado): quase o próprio fundo
const TERRENO = { fill: '#e3e7dd', stroke: '#d7ddd0' }

// ---- Lente MÓDULOS: uma cor por SITUAÇÃO DO MÓDULO ----------------------
// Mesmo vocabulário (e mesmas cores) dos selos de status usados no app e no web:
// ocupado (vermelho) · disponível (azul) · indisponível (amarelo).
const MODSIT = {
  ocupado:      { fill: '#ffe5e5', stroke: '#c22828', dot: '#f33a3a', label: 'ocupado' },
  disponivel:   { fill: '#caeffc', stroke: '#2280a8', dot: '#46a7d0', label: 'disponível' },
  indisponivel: { fill: '#fffae5', stroke: '#645400', dot: '#f3d430', label: 'indisponível' },
}

// ---- Estado de cada piquete de um módulo --------------------------------
// Fonte única: mesma regra de rodízio usada na tela de Consultar.
function piqEstados(mod, ms) {
  const Pa = MAPA_P()
  const status = Pa.statusDe(mod, ms)
  const lotes = Pa.lotesDoMod(mod)
  const RD = Pa.RODIZIO_DIAS
  const a = mod.atual
  // módulo vazio: descanso = dias desde a última saída registrada
  let saidaDias = 0
  if (!a) {
    const s = Pa.MOVIMENTOS.find((x) => x.fazenda === mod.fazenda && x.moduloNome === mod.nome && x.tipo === 'saida')
    saidaDias = s ? Pa.diasEntre(s.data) : 0
  }
  const out = {}
  mod.piquetes.forEach((p, idx) => {
    const lote = lotes.find((l) => l.piquete === p) || null
    let descanso = 0
    if (a) {
      const n = mod.piquetes.length
      let base = a.diasModulo - (idx + 1) * RD
      if (base < 0) base += n * RD
      descanso = Math.max(0, base)
    } else descanso = saidaDias
    // Regra da lente "Situação": só há DESCANSO dentro de um módulo ocupado (rodízio).
    // Módulo sem lote = piquetes "Sem lote" (disponível) — distinção pedida em campo.
    // 3 estados: com lote · em descanso (= sem lote) · indisponível
    const sit = status === 'indisponivel' ? 'indisponivel' : lote ? 'lote' : 'descanso'
    out[p] = { lote, descanso, sit }
  })
  return { status, lotes, piq: out }
}

// ---- Chip de legenda ----------------------------------------------------
const LegChip = ({ c, children }) => (
  <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-grayish-dark)', whiteSpace: 'nowrap' }}>
    <span style={{ width: 14, height: 14, borderRadius: 3, background: c.fill, border: '1.5px solid ' + c.stroke, flexShrink: 0 }} />{children}
  </span>
)

// ---- Botão flutuante do mapa -------------------------------------------
const MapBtn = ({ icon, node, label, onClick, active }) => {
  const { Icon } = MAPA_DS()
  return (
    <button onClick={onClick} aria-label={label} style={{ width: 44, height: 44, display: 'flex', alignItems: 'center', justifyContent: 'center',
      border: '1px solid ' + (active ? 'var(--color-primary)' : 'rgba(0,0,0,0.12)'), background: active ? 'var(--color-primary)' : 'rgba(255,255,255,0.96)',
      borderRadius: 'var(--border-radius-default)', boxShadow: '0 2px 8px rgba(0,0,0,0.18)', cursor: 'pointer', padding: 0 }}>
      {node || <Icon name={icon} size={20} color={active ? '#fff' : 'var(--color-grayish-dark)'} />}
    </button>
  )
}

// ============================================================
// MAPA
// ============================================================
function Mapa({ fazenda, gps, lente: lenteInicial, onPiquete, onModulo, onHistModulo, onMover, onClose, onSecoes, root, leading, onTab, onModules, onExit, fazModal }) {
  const { Icon } = MAPA_DS()
  const Pa = MAPA_P()
  const { useState, useRef, useMemo, useEffect } = React
  const GEO = useMemo(() => (window.PAST_GEO_FOR ? window.PAST_GEO_FOR(fazenda) : null), [fazenda])
  const lente = 'situacao'   // uma única classificação no mapa: situação do piquete
  // Duas leituras do MESMO mapa: por piquete (padrão) ou por módulo.
  const [modo, setModo] = useState('piquetes')  // 'piquetes' | 'modulos'
  const porMod = modo === 'modulos'
  const [busca, setBusca] = useState('')
  const [sel, setSel] = useState(null)         // { kind:'piquete'|'modulo', modId, piquete? }
  const [vb, setVb] = useState(null)
  const [legenda, setLegenda] = useState(true)
  const svgRef = useRef(null)
  const drag = useRef(null)
  const moved = useRef(false)

  const ms = useMemo(() => Pa.initModState(), [])
  const mods = useMemo(() => Pa.secoesDe(fazenda).flatMap((s) => s.modulos), [fazenda])
  const est = useMemo(() => {
    const o = {}
    mods.forEach((m) => { o[m.id] = piqEstados(m, ms) })
    return o
  }, [mods, ms])

  const geom = useMemo(() => (GEO && GEO.polys ? mapaGeom(GEO.polys) : null), [GEO])

  // grupo de cada polígono = módulo ativo a que pertence
  const grupoDe = useMemo(() => {
    const modOf = {}
    if (GEO) GEO.polys.forEach((p) => { const a = GEO.assign[p.id]; if (a && est[a.modId] && est[a.modId].status !== 'arquivado') modOf[p.id] = String(a.modId) })
    return (id) => (modOf[id] === undefined ? null : modOf[id])
  }, [GEO, est])

  // cor do módulo: módulos vizinhos nunca recebem a mesma cor — é o que impede
  // dois blocos colados de se lerem como um só.
  const corMod = useMemo(() => {
    const cor = {}
    if (!GEO || !geom) return () => MOD_CORES[0]
    const viz = {}, tam = {}
    GEO.polys.forEach((p) => {
      const g = grupoDe(p.id); if (g == null) return
      tam[g] = (tam[g] || 0) + 1
      const arr = geom.pts[p.id]; if (!arr) return
      for (let i = 0; i < arr.length; i++) {
        const a = arr[i], b = arr[(i + 1) % arr.length]
        const dx = b[0] - a[0], dy = b[1] - a[1], len = Math.hypot(dx, dy); if (len < 0.6) continue
        const mx = (a[0] + b[0]) / 2, my = (a[1] + b[1]) / 2, nx = -dy / len, ny = dx / len
        const sgn = ((p.cx - mx) * nx + (p.cy - my) * ny) > 0 ? -1 : 1
        const o = geom.celulaEm(mx + nx * 2.6 * sgn, my + ny * 2.6 * sgn)
        const og = o == null ? null : grupoDe(o)
        if (og != null && og !== g) (viz[g] || (viz[g] = new Set())).add(og)
      }
    })
    Object.keys(tam).sort((a, b) => tam[b] - tam[a]).forEach((g, i) => {
      const usadas = new Set([...(viz[g] || [])].map((o) => cor[o]).filter(Boolean))
      const pref = MOD_CORES[i % MOD_CORES.length]
      cor[g] = usadas.has(pref) ? (MOD_CORES.find((c) => !usadas.has(c)) || pref) : pref
    })
    return (modId) => cor[String(modId)] || MOD_CORES[0]
  }, [GEO, geom, grupoDe])

  const contornos = useMemo(() => (geom ? geom.contornos(grupoDe) : []), [geom, grupoDe])

  const semMapa = !GEO || !GEO.allBox

  // "Você está aqui": centroide de um piquete ocupado (demo do GPS).
  const meuPonto = useMemo(() => {
    if (gps !== 'ok' || !GEO || !GEO.allBox) return null
    const occ = mods.find((m) => est[m.id].lotes.length)
    if (!occ) return null
    const ids = GEO.modCells[occ.id] || []
    const cell = ids.map((i) => GEO.byId[i]).find((c) => c && GEO.assign[c.id].piquete === est[occ.id].lotes[0].piquete) || GEO.byId[ids[0]]
    return cell ? { cx: cell.cx, cy: cell.cy, mod: occ } : null
  }, [gps, GEO, mods, est])

  // enquadramento inicial: toda a área cadastrada, com folga
  const fit = () => {
    if (!GEO || !GEO.allBox) return
    const b = GEO.allBox, pad = 26
    setVb({ x: b.x0 - pad, y: b.y0 - pad, w: (b.x1 - b.x0) + pad * 2, h: (b.y1 - b.y0) + pad * 2 })
  }
  // Zoom próximo em volta do operador — em campo o mapa abre onde ele está.
  const focusPonto = () => {
    if (!meuPonto || !GEO || !GEO.allBox) return
    const w = Math.max(90, (GEO.allBox.x1 - GEO.allBox.x0) / 5)
    const h = w * (GEO.allBox.y1 - GEO.allBox.y0) / (GEO.allBox.x1 - GEO.allBox.x0)
    setVb({ x: meuPonto.cx - w / 2, y: meuPonto.cy - h / 2, w, h })
  }
  useEffect(() => { if (semMapa) return; meuPonto ? focusPonto() : fit() }, [semMapa, meuPonto])

  const focusMod = (modId) => {
    const b = GEO.modBox[modId]; if (!b) return
    const pad = Math.max(18, (b.x1 - b.x0) * 0.28)
    setVb({ x: b.x0 - pad, y: b.y0 - pad, w: (b.x1 - b.x0) + pad * 2, h: (b.y1 - b.y0) + pad * 2 })
  }

  // ---- pan / zoom (toque e mouse) ----
  const zoom = (f, cx, cy) => setVb((v) => {
    if (!v) return v
    const w = Math.min(1400, Math.max(70, v.w * f)), h = w * (v.h / v.w)
    const px = cx == null ? 0.5 : cx, py = cy == null ? 0.5 : cy
    return { x: v.x + (v.w - w) * px, y: v.y + (v.h - h) * py, w, h }
  })
  const onDown = (e) => {
    moved.current = false
    const t = e.touches ? e.touches : [e]
    if (t.length === 2) {
      drag.current = { pinch: true, d0: Math.hypot(t[0].clientX - t[1].clientX, t[0].clientY - t[1].clientY), vb }
    } else {
      drag.current = { x: t[0].clientX, y: t[0].clientY, vb }
    }
  }
  const onMove = (e) => {
    const d = drag.current; if (!d || !vb) return
    const t = e.touches ? e.touches : [e]
    const rect = svgRef.current.getBoundingClientRect()
    if (d.pinch && t.length === 2) {
      const dd = Math.hypot(t[0].clientX - t[1].clientX, t[0].clientY - t[1].clientY)
      const f = d.d0 / dd
      const w = Math.min(1400, Math.max(70, d.vb.w * f)), h = w * (d.vb.h / d.vb.w)
      setVb({ x: d.vb.x + (d.vb.w - w) / 2, y: d.vb.y + (d.vb.h - h) / 2, w, h })
      moved.current = true
      return
    }
    const dx = (t[0].clientX - d.x) / rect.width * d.vb.w
    const dy = (t[0].clientY - d.y) / rect.height * d.vb.h
    if (Math.abs(dx) + Math.abs(dy) > 1) moved.current = true
    setVb({ ...d.vb, x: d.vb.x - dx, y: d.vb.y - dy })
  }
  const onUp = () => { drag.current = null }

  if (semMapa) {
    return (
      <window.MPhone title="Mapa" onBack={onClose} footer={<window.PastTabBar active="consultar" onTab={onTab} />} modal={fazModal}>
        <div style={{ margin: 'auto 0', textAlign: 'center', padding: '0 10px' }}>
          <Icon name="information-circle-outline" size={40} color="var(--color-neutral-300)" />
          <div style={{ fontSize: 17, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-grayish-dark)', marginTop: 12 }}>Mapa não cadastrado</div>
          <div style={{ fontSize: 14, color: 'var(--color-grayish)', marginTop: 6, lineHeight: 1.5 }}>
            A fazenda {fazenda} ainda não teve o mapa importado e os piquetes desenhados no sistema web.
          </div>
        </div>
      </window.MPhone>
    )
  }

  const nLote = mods.filter((m) => est[m.id].lotes.length).length
  const totalCab = mods.reduce((a, m) => a + est[m.id].lotes.reduce((b, l) => b + l.qtd, 0), 0)
  // Contagem por situação do módulo — legenda da lente "por módulos".
  const nPorStatus = { ocupado: 0, disponivel: 0, indisponivel: 0 }
  mods.forEach((m) => { const s = est[m.id].status; if (nPorStatus[s] != null) nPorStatus[s]++ })

  // pin do "você está aqui" — calculado acima (usado no enquadramento inicial)

  // ---- estilo de um polígono ----
  const styleOf = (a) => {
    const e = est[a.modId]; if (!e) return null
    if (e.status === 'arquivado') return TERRENO   // fora de uso: some no fundo
    const s = e.piq[a.piquete]; if (!s) return null
    if (lente === 'descanso') {
      if (s.lote) return { ...SIT.lote, cattle: true }
      if (s.sit === 'indisponivel') return SIT.indisponivel
      return rampDe(s.descanso)
    }
    // dois canais que não competem: ESTADO no preenchimento, MÓDULO na tinta.
    const c = corMod(a.modId)
    if (s.sit === 'lote') return { fill: SIT.lote.fill, fo: 1, stroke: modDark(c, 0.5), so: 0.9 }
    if (s.sit === 'indisponivel') return { hatch: true, fo: 1, stroke: SIT.indisponivel.stroke, so: 0.8 }
    return { fill: c, fo: 0.22, stroke: c, so: 0.55 }
  }

  const zoomFactor = vb ? (GEO.allBox.x1 - GEO.allBox.x0) / vb.w : 1
  const detalhe = zoomFactor > 1.7   // zoom suficiente p/ letra do piquete
  // k converte px de tela em unidades do viewBox: rótulos e traços mantêm o
  // mesmo tamanho aparente em qualquer zoom (mapa lido de relance, em pé).
  const k = vb ? vb.w / 366 : 1

  const selMod = sel ? mods.find((m) => m.id === sel.modId) : null

  // ---- Ficha (bottom sheet) ----
  const ficha = (() => {
    if (!selMod) return null
    const e = est[selMod.id]
    // Esquerda: título (lugar) + seção logo abaixo. Direita: selo de situação + fechar.
    // Linha 1: título (largura toda, só o X à direita). Linha 2: seção à esquerda, selo à direita.
    // Linha 1: título à esquerda + selo à direita. Linha 2: seção.
    const head = (t, s, selo) => (
      <div style={{ minWidth: 0, flex: 1, display: 'flex', flexDirection: 'column', gap: 3 }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 }}>
          <span style={{ minWidth: 0, fontSize: 20, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-grayish-dark)', lineHeight: 1.2, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>{t}</span>
          {selo}
        </div>
        <span style={{ fontSize: 13, color: 'var(--color-grayish)' }}>{s}</span>
      </div>
    )
    const badge = (label, bg, fg) => (
      <span style={{ whiteSpace: 'nowrap', fontSize: 11, fontWeight: 'var(--font-weight-bold)', textTransform: 'uppercase', letterSpacing: 'var(--letter-space-xs)', padding: '4px 10px', borderRadius: 99, background: bg, color: fg }}>{label}</span>
    )
    let corpo, acoes, selo
    const metric = (v, u, l, cor) => (
      <div style={{ minWidth: 0 }}>
        <div style={{ display: 'flex', alignItems: 'baseline', gap: 5 }}>
          <span style={{ fontSize: 32, fontWeight: 'var(--font-weight-bold)', color: cor || 'var(--color-grayish-dark)', lineHeight: 1 }}>{v}</span>
          <span style={{ fontSize: 14, fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-grayish)' }}>{u}</span>
        </div>
        <div style={{ fontSize: 14, color: 'var(--color-grayish-dark)', marginTop: 5, textWrap: 'pretty' }}>{l}</div>
      </div>
    )
    const duasCols = (a, b) => (
      <div style={{ display: 'flex', flexDirection: 'column' }}>{a}{b}</div>
    )
    if (sel.kind === 'piquete') {
      const s = e.piq[sel.piquete]
      selo = s.lote ? badge('Pastejando', 'var(--color-primary)', '#fff')
        : s.sit === 'indisponivel' ? badge('Indisponível', 'var(--color-alert-light-100)', 'var(--color-alert-dark)')
        : s.descanso > 0 ? badge('Descansando', 'var(--color-primary-30)', 'var(--color-success-dark)')
        : badge('Sem lote', 'var(--color-neutral-light-500)', 'var(--color-grayish-dark)')
      const metricLinha = (txt) => <div style={{ fontSize: 14, color: 'var(--color-grayish)', marginTop: 6 }}>{txt}</div>
      corpo = s.lote
        ? (
          <>
            {metric(Pa.fmt(s.lote.qtd), 'cabeças de ' + s.lote.categoria, '', 'var(--color-primary)')}
            {metricLinha(`Há ${s.lote.diasPiquete} ${s.lote.diasPiquete === 1 ? 'dia' : 'dias'} neste piquete`)}
          </>
        )
        : s.sit === 'indisponivel'
        ? <div style={{ fontSize: 15, color: 'var(--color-grayish-dark)' }}>{(Pa.indispAtivo(ms[selMod.id]) || {}).motivo || 'Fora de uso'}</div>
        : s.descanso > 0
        ? metric(Pa.fmt(s.descanso), (s.descanso === 1 ? 'dia' : 'dias') + ' de descanso', '')
        : <div style={{ fontSize: 15, color: 'var(--color-grayish-dark)' }}>Disponível para entrada</div>
      acoes = (
        <>
          <window.MButton onClick={() => onPiquete(selMod, sel.piquete)}>Ver histórico</window.MButton>
        </>
      )
    } else {
      const cab = e.lotes.reduce((a, l) => a + l.qtd, 0)
      const nOcup = e.lotes.length
      selo = e.status === 'indisponivel' ? badge('Indisponível', 'var(--color-alert-light-100)', 'var(--color-alert-dark)')
        : nOcup ? badge('Ocupado', 'var(--color-primary)', '#fff')
        : badge('Disponível', 'var(--color-info-light-100)', 'var(--color-info-dark)')
      corpo = (
        <>
          {metric(Pa.fmt(cab), 'cabeças', nOcup ? e.lotes.map((l) => `${Pa.fmt(l.qtd)} ${l.categoria} no piquete ${l.piquete}`).join(' · ') : 'nenhum lote no módulo', nOcup ? 'var(--color-primary)' : undefined)}
          <div style={{ fontSize: 14, color: 'var(--color-grayish)', marginTop: 6 }}>{selMod.piquetes.length} piquetes · {Pa.fmt(selMod.area)} ha{cab ? ` · ${Pa.fmt(Math.round(e.lotes.reduce((a, l) => a + l.ua, 0)))} UA` : ''}</div>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, minHeight: 40, marginTop: 12, paddingTop: 10, borderTop: 'var(--border-neutral-light)' }}>
            <span style={{ flex: 1, minWidth: 0, fontSize: 14, color: 'var(--color-grayish)' }}>piquetes com lote</span>
            <span style={{ flexShrink: 0, fontSize: 16, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-grayish-dark)' }}>{nOcup}/{selMod.piquetes.length}</span>
          </div>
        </>
      )
      acoes = (
        <>
          <window.MButton onClick={() => onModulo(selMod)}>Ver módulo {selMod.nome}</window.MButton>
          <window.MButton variant="ghost" onClick={() => onHistModulo(selMod)}>Histórico do módulo</window.MButton>
        </>
      )
    }
    return (
      <div style={{ position: 'absolute', inset: 0, zIndex: 60, display: 'flex', flexDirection: 'column', justifyContent: 'flex-end' }}>
        <div onClick={() => setSel(null)} style={{ position: 'absolute', inset: 0, background: 'rgba(0,0,0,0.28)' }} />
        <div style={{ position: 'relative', background: '#fff', borderRadius: '20px 20px 0 0', padding: '14px 18px 20px', boxShadow: '0 -8px 26px rgba(0,0,0,0.22)', animation: 'fgSheet .22s ease-out' }}>
          <div style={{ width: 40, height: 4, borderRadius: 99, background: 'var(--color-neutral-200)', margin: '0 auto 12px' }} />
          <div style={{ display: 'flex', alignItems: 'flex-start', gap: 10, marginBottom: 14 }}>
            {sel.kind === 'piquete'
              ? head(`Módulo ${selMod.nome} - Piquete ${sel.piquete}`, `Seção ${selMod.secao}`, selo)
              : head('Módulo ' + selMod.nome, `Seção ${selMod.secao}`, selo)}
          </div>
          <div style={{ display: 'flex', flexDirection: 'column' }}>{corpo}</div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 16 }}>{acoes}</div>
        </div>
      </div>
    )
  })()

  // ---- Busca: módulo ou piquete pelo nome, sem passar pela seleção de seção ----
  const q = busca.trim().toLowerCase()
  const achados = !q ? [] : (() => {
    const r = []
    mods.forEach((m) => {
      if ((m.nome + ' ' + m.secao).toLowerCase().includes(q)) r.push({ kind: 'modulo', m })
      m.piquetes.forEach((p) => {
        if ((m.nome + ' ' + p).toLowerCase().includes(q) || p.toLowerCase().includes(q)) r.push({ kind: 'piquete', m, p })
      })
    })
    return r.slice(0, 12)
  })()
  const abrirAchado = (a) => {
    setBusca('')
    focusMod(a.m.id)
    setSel(a.kind === 'piquete' ? { kind: 'piquete', modId: a.m.id, piquete: a.p } : { kind: 'modulo', modId: a.m.id })
  }

  const header = (
    <div style={{ position: 'relative', zIndex: 30, padding: '10px 14px 10px', background: '#fff', borderBottom: 'var(--border-neutral-light)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', alignItems: 'flex-start', gap: 6 }}>
            <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 12, fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-grayish-dark)', whiteSpace: 'nowrap' }}>
              <span style={{ display: 'inline-flex', gap: 3 }}>
                {MOD_CORES.slice(0, 3).map((c) => <span key={c} style={{ width: 14, height: 14, borderRadius: 3, background: c, opacity: 0.3, border: '1.5px solid ' + modDark(c, 0.66), flexShrink: 0 }} />)}
              </span>Sem lote - cor do módulo
            </span>
            <LegChip c={{ fill: SIT.indisponivel.fill, stroke: SIT.indisponivel.stroke }}>Indisponível</LegChip>
            <LegChip c={{ fill: SIT.lote.fill, stroke: SIT.lote.stroke }}>Com lote</LegChip>
          </div>
        {onSecoes && (
          <window.FazendaGuanabaraDesignSystem_b62f7e.Button type="outline" onClick={onSecoes} style={{ minHeight: 44, padding: '0 14px', flexShrink: 0, whiteSpace: 'nowrap' }}>Por seção</window.FazendaGuanabaraDesignSystem_b62f7e.Button>
        )}
      </div>
      {!!q && (
        <div style={{ position: 'absolute', left: 14, right: 14, top: '100%', marginTop: 6, background: '#fff', border: 'var(--border-neutral-light)', borderRadius: 'var(--border-radius-default)', boxShadow: '0 8px 22px rgba(0,0,0,0.16)', maxHeight: 300, overflowY: 'auto', zIndex: 40 }}>
          {achados.length ? achados.map((a, i) => (
            <div key={a.kind + a.m.id + (a.p || '') + i} onClick={() => abrirAchado(a)}
              style={{ display: 'flex', alignItems: 'center', gap: 10, minHeight: 52, padding: '8px 14px', cursor: 'pointer', borderTop: i ? 'var(--border-neutral-light)' : 'none' }}>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 15, fontWeight: 'var(--font-weight-bold)', color: 'var(--color-grayish-dark)' }}>{a.kind === 'piquete' ? 'Piquete ' + a.p : 'Módulo ' + a.m.nome}</div>
                <div style={{ fontSize: 13, color: 'var(--color-grayish)' }}>{a.kind === 'piquete' ? `Módulo ${a.m.nome} · Seção ${a.m.secao}` : `Seção ${a.m.secao} · ${a.m.piquetes.length} piquetes`}</div>
              </div>
              <Icon name="chevron-forward" size={18} color="var(--color-neutral-300)" />
            </div>
          )) : (
            <div style={{ padding: '16px 14px', fontSize: 14, color: 'var(--color-grayish)' }}>Nada encontrado para “{busca}”.</div>
          )}
        </div>
      )}
    </div>
  )

  return (
    <window.MPhone title={root ? 'Lotação atual' : 'Mapa da fazenda'} onBack={root ? undefined : onClose}
      leadingNode={root ? leading : undefined} actionIcon={root ? 'exit-outline' : undefined} onAction={root ? onExit : undefined}
      footer={<window.PastTabBar active="consultar" onTab={onTab} />} modal={fazModal || ficha}>
      <div style={{ margin: '-20px -20px 0', flex: 1, display: 'flex', flexDirection: 'column', minHeight: 0 }}>
        {header}
        <div style={{ position: 'relative', flex: 1, minHeight: 0, background: '#e6eae0', backgroundImage: 'radial-gradient(circle, rgba(120,134,110,0.22) 1px, transparent 1.2px)', backgroundSize: '22px 22px', overflow: 'hidden' }}>
          <svg ref={svgRef} viewBox={vb ? `${vb.x} ${vb.y} ${vb.w} ${vb.h}` : `0 0 ${GEO.W} ${GEO.H}`}
            width="100%" height="100%" style={{ display: 'block', touchAction: 'none', cursor: 'grab' }}
            onMouseDown={onDown} onMouseMove={onMove} onMouseUp={onUp} onMouseLeave={onUp}
            onTouchStart={onDown} onTouchMove={onMove} onTouchEnd={onUp}
            onWheel={(e) => { e.preventDefault(); zoom(e.deltaY > 0 ? 1.12 : 0.89) }}>
            <defs>
              <pattern id="mapaHatch" width="6" height="6" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
                <rect width="6" height="6" fill="#ded9c8" />
                <line x1="0" y1="0" x2="0" y2="6" stroke="#b3ad98" strokeWidth="1.6" />
              </pattern>
            </defs>

            {/* área fora do cadastro — contexto do terreno */}
            {GEO.polys.filter((p) => !GEO.assign[p.id]).map((p) => (
              <path key={'u' + p.id} d={p.d} fill={TERRENO.fill} stroke={TERRENO.stroke} strokeWidth={0.5 * k} pointerEvents="none" />
            ))}
            {/* rios */}
            {GEO.rivers.map((d, i) => <path key={'r' + i} d={d} fill="none" stroke="#7fb0cf" strokeWidth={1.3 * k} strokeLinecap="round" opacity="0.9" pointerEvents="none" />)}

            {/* piquetes cadastrados — cor pela lente (leitura por piquete) */}
            {!porMod && GEO.polys.map((p) => {
              const a = GEO.assign[p.id]; if (!a) return null
              const s = styleOf(a); if (!s) return null
              const isSel = sel && sel.kind === 'piquete' && sel.modId === a.modId && sel.piquete === a.piquete
              return (
                <path key={'p' + p.id} d={p.d}
                  fill={s.hatch ? 'url(#mapaHatch)' : s.fill} fillOpacity={s.fo == null ? 1 : s.fo}
                  stroke={isSel ? '#111' : s.stroke} strokeOpacity={isSel ? 1 : (s.so == null ? 1 : s.so)} strokeWidth={(isSel ? 2.6 : 0.5) * k}
                  strokeDasharray={s.dash ? (2.5 * k) + ' ' + (2 * k) : undefined} strokeLinejoin="round"
                  onClick={(e) => { e.stopPropagation(); if (!moved.current) setSel({ kind: 'piquete', modId: a.modId, piquete: a.piquete }) }}
                  style={{ cursor: 'pointer' }} />
              )
            })}

            {/* leitura por MÓDULO: bloco inteiro colorido pela situação do módulo.
                1ª passada = traço grosso; a 2ª (preenchimento) cobre os traços
                internos, deixando visível só o contorno do módulo. A 3ª devolve a
                estrutura interna (divisão dos piquetes) em traço fino. */}
            {porMod && mods.filter((m) => est[m.id].status !== 'arquivado').map((m) => {
              const c = MODSIT[est[m.id].status] || MODSIT.disponivel
              const isSel = sel && sel.modId === m.id
              const cells = (GEO.modCells[m.id] || []).map((cid) => GEO.byId[cid]).filter(Boolean)
              return (
                <g key={'mg' + m.id}>
                  {cells.map((p) => (
                    <path key={'mf' + p.id} d={p.d} fill={c.fill} stroke={c.stroke} strokeOpacity={detalhe ? 0.3 : 0} strokeWidth={0.4 * k} strokeLinejoin="round"
                      onClick={(e) => { e.stopPropagation(); if (!moved.current) setSel({ kind: 'modulo', modId: m.id }) }}
                      style={{ cursor: 'pointer' }} />
                  ))}
                </g>
              )
            })}

            {/* CONTORNO DO MÓDULO: calculado por lado (quem está do outro lado da
                divisa). Fecha o bloco na cor do módulo e não deixa traço solto
                no meio, mesmo com divisas subdivididas de formas diferentes. */}
            {!porMod && contornos.map((o) => {
              const isSel = sel && String(sel.modId) === o.g
              return <path key={'out' + o.g} d={o.d} fill="none" stroke={isSel ? '#111' : modDark(corMod(o.g), 0.66)}
                strokeWidth={(isSel ? 2.6 : 1.7) * k} strokeLinecap="round" strokeLinejoin="round" pointerEvents="none" />
            })}

            {porMod && contornos.map((o) => {
              const e = est[o.g] || est[+o.g]
              const c = MODSIT[(e && e.status) || 'disponivel'] || MODSIT.disponivel
              const isSel = sel && String(sel.modId) === o.g
              return <path key={'mout' + o.g} d={o.d} fill="none" stroke={isSel ? '#111' : c.stroke}
                strokeWidth={(isSel ? 3 : 2) * k} strokeLinecap="round" strokeLinejoin="round" pointerEvents="none" />
            })}

            {meuPonto && (
              <g pointerEvents="none">
                <circle cx={meuPonto.cx} cy={meuPonto.cy} r={16 * k} fill="#2f7cd6" opacity="0.16" />
                <circle cx={meuPonto.cx} cy={meuPonto.cy} r={6.5 * k} fill="#2f7cd6" stroke="#fff" strokeWidth={2.6 * k} />
              </g>
            )}

            {/* número de cabeças dentro do piquete ocupado (sem empilhar rótulos).
                É o dado mais importante do mapa — tamanho constante em qualquer zoom.
                Desenhado depois do ponto do GPS para nunca ficar por baixo dele. */}
            {!porMod && (() => {
              const postos = []
              return GEO.polys.map((p) => {
              const a = GEO.assign[p.id]; if (!a) return null
              const e = est[a.modId]; if (!e || e.status === 'arquivado') return null
              const s = e.piq[a.piquete]
              if (!s || !s.lote || a.dup) return null
              const folga = 34 * k
              if (postos.some((q) => Math.abs(q.x - p.cx) < folga && Math.abs(q.y - p.cy) < 15 * k)) return null
              postos.push({ x: p.cx, y: p.cy })
              // não cobrir o ponto "você está aqui": sobe o número quando coincidem
              const perto = meuPonto && Math.abs(meuPonto.cx - p.cx) < 26 * k && Math.abs(meuPonto.cy - p.cy) < 20 * k
              return (
                <text key={'c' + p.id} x={p.cx} y={p.cy + 5.5 * k - (perto ? 20 * k : 0)} textAnchor="middle" fontSize={16 * k} fontWeight="800"
                  fontFamily="var(--font-family)" fill="#fff" pointerEvents="none"
                  style={{ paintOrder: 'stroke', stroke: 'rgba(20,28,18,0.55)', strokeWidth: 3.4 * k }}>{Pa.fmt(s.lote.qtd)}</text>
              )
            })
            })()}

            {/* etiqueta do módulo (leitura por módulo): nome + cabeças.
                Mesma regra do rótulo de cabeças: não empilhar — a etiqueta que
                colidiria com outra já colocada é omitida (aparece ao aproximar). */}
            {porMod && (() => {
              const postos = []
              return mods.filter((m) => est[m.id].status !== 'arquivado').map((m) => {
              const e = est[m.id]
              const c = MODSIT[e.status] || MODSIT.disponivel
              const cells = (GEO.modCells[m.id] || []).map((cid) => GEO.byId[cid]).filter(Boolean)
              if (!cells.length) return null
              const cx = cells.reduce((a, p) => a + p.cx, 0) / cells.length
              const cy = cells.reduce((a, p) => a + p.cy, 0) / cells.length
              const cab = e.lotes.reduce((a, l) => a + l.qtd, 0)
              const txt = `${m.nome}   ${cab ? Pa.fmt(cab) + ' cab.' : 'sem lote'}`
              const fs = 12.5 * k
              const w = (txt.length * 6.9 + 26) * k
              const h = 20 * k
              const box = { x0: cx - w / 2, x1: cx + w / 2, y0: cy - h / 2, y1: cy + h / 2 }
              const folga = 3 * k
              if (postos.some((q) => box.x0 < q.x1 + folga && box.x1 > q.x0 - folga && box.y0 < q.y1 + folga && box.y1 > q.y0 - folga)) return null
              postos.push(box)
              return (
                <g key={'pin' + m.id} onClick={(ev) => { ev.stopPropagation(); if (!moved.current) setSel({ kind: 'modulo', modId: m.id }) }} style={{ cursor: 'pointer' }}>
                  <rect x={box.x0} y={box.y0} width={w} height={h} rx={h / 2} fill="#fff" stroke={c.stroke} strokeWidth={0.9 * k} />
                  <circle cx={box.x0 + 9 * k} cy={cy} r={3.2 * k} fill={c.dot} />
                  <text x={box.x0 + 17 * k} y={cy + fs * 0.35} fontSize={fs} fontWeight="800" fontFamily="var(--font-family)" fill="#2b3327" pointerEvents="none">{txt}</text>
                </g>
              )
            })
            })()}

            {/* você está aqui */}
          </svg>

          {/* controles flutuantes */}
          <div style={{ position: 'absolute', top: 12, right: 12, display: 'flex', flexDirection: 'column', gap: 8 }}>
            <MapBtn icon="add" label="Aproximar" onClick={() => zoom(0.72)} />
            <MapBtn icon="remove" label="Afastar" onClick={() => zoom(1.38)} />
            <MapBtn label="Ver fazenda toda" onClick={fit} node={
              <svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="var(--color-grayish-dark)" strokeWidth="1.8" strokeLinecap="round">
                <path d="M3 7V3h4M17 7V3h-4M3 13v4h4M17 13v4h-4" />
              </svg>} />
            {meuPonto && <MapBtn label="Onde estou" onClick={focusPonto} node={
              <svg width="20" height="20" viewBox="0 0 20 20" fill="none" stroke="#2f7cd6" strokeWidth="1.8" strokeLinecap="round">
                <circle cx="10" cy="10" r="3.4" fill="#2f7cd6" stroke="none" />
                <circle cx="10" cy="10" r="7" />
              </svg>} />}
          </div>

        </div>
      </div>
    </window.MPhone>
  )
}

window.PastMapa = Mapa
