import { useEffect, useRef, useState, useCallback } from 'react'
import { api } from '../api'

async function fetchAllRows(source) {
  const res = await api.getViewData(source, 100000, 0)
  return res.rows || []
}

let perspectivePromise = null

function loadPerspective() {
  if (perspectivePromise) return perspectivePromise
  perspectivePromise = (async () => {
    if (!document.getElementById('psp-theme')) {
      const link = document.createElement('link')
      link.id = 'psp-theme'
      link.rel = 'stylesheet'
      link.crossOrigin = 'anonymous'
      link.href = 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer/dist/css/themes.css'
      document.head.appendChild(link)
    }
    const [{ default: perspective }] = await Promise.all([
      import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/client@4.4.0/dist/cdn/perspective.js'),
      import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer@4.4.0/dist/cdn/perspective-viewer.js'),
      import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer-datagrid@4.4.0/dist/cdn/perspective-viewer-datagrid.js'),
      import(/* @vite-ignore */ 'https://cdn.jsdelivr.net/npm/@perspective-dev/viewer-d3fc@4.4.0/dist/cdn/perspective-viewer-d3fc.js'),
    ])
    return perspective
  })()
  return perspectivePromise
}

function formatVal(v) {
  if (v == null) return null
  if (typeof v === 'number' && v > 1e11 && v < 2e12) {
    const d = new Date(v)
    if (!isNaN(d)) return d.toISOString().slice(0, 10)
  }
  return String(v)
}

function normalize(v) {
  if (v == null) return null
  if (typeof v === 'number' && v > 1e11 && v < 2e12) return new Date(v).toISOString().slice(0, 10)
  return String(v).trim()
}

function filterRowsByConfig(allRows, filters) {
  if (!filters || filters.length === 0) return allRows
  const knownFields = allRows.length > 0 ? new Set(Object.keys(allRows[0])) : new Set()
  const applicable = filters.filter(([field]) => knownFields.has(field))
  if (applicable.length === 0) return allRows
  return allRows.filter(row =>
    applicable.every(([field, op, value]) => {
      const rawVal = row[field]
      if (rawVal == null) return op === '!=' || op === 'not contains'
      const a = normalize(rawVal)
      const b = value != null ? String(value).trim() : ''
      const aNum = parseFloat(a), bNum = parseFloat(b)
      const numeric = !isNaN(aNum) && !isNaN(bNum)
      switch (op) {
        case '==': return a === b
        case '!=': return a !== b
        case '>':  return numeric ? aNum > bNum : a > b
        case '>=': return numeric ? aNum >= bNum : a >= b
        case '<':  return numeric ? aNum < bNum : a < b
        case '<=': return numeric ? aNum <= bNum : a <= b
        case 'contains': return a.toLowerCase().includes(b.toLowerCase())
        case 'not contains': return !a.toLowerCase().includes(b.toLowerCase())
        default: return true
      }
    })
  )
}

const LAYOUT_KEY = (source) => `psp_layout_${source}`
const DEFAULT_PLUGIN_CONFIG = { edit_mode: 'SELECT_ROW' }

export default function Pivot({ source }) {
  const viewerRef = useRef()
  const workerRef = useRef()
  const allRowsRef = useRef([])
  const [status, setStatus] = useState('idle')
  const [error, setError] = useState('')
  const [inspectedRows, setInspectedRows] = useState(null)
  const [clickDetail, setClickDetail] = useState(null)

  // Named layouts
  const [layouts, setLayouts] = useState([])
  const [activeLayoutId, setActiveLayoutId] = useState(null)
  const [saveAsName, setSaveAsName] = useState('')
  const [showSaveAs, setShowSaveAs] = useState(false)
  const [layoutMsg, setLayoutMsg] = useState('')

  const flashMsg = (msg) => {
    setLayoutMsg(msg)
    setTimeout(() => setLayoutMsg(''), 2000)
  }

  const loadLayouts = useCallback(async () => {
    if (!source) return
    try {
      const rows = await api.getPivotLayouts(source)
      setLayouts(rows)
    } catch {}
  }, [source])

  useEffect(() => {
    if (!source) return
    let cancelled = false
    setInspectedRows(null)
    setClickDetail(null)
    setActiveLayoutId(null)
    setShowSaveAs(false)
    allRowsRef.current = []

    loadLayouts()

    async function init() {
      setStatus('loading')
      setError('')
      try {
        const [perspective, rows] = await Promise.all([
          loadPerspective(),
          fetchAllRows(source),
        ])
        if (cancelled) return
        if (!rows.length) { setStatus('noview'); return }

        allRowsRef.current = rows

        if (workerRef.current) { try { workerRef.current.terminate() } catch {} }

        const worker = await perspective.worker()
        if (cancelled) { worker.terminate(); return }
        workerRef.current = worker

        await worker.table(rows, { name: source })
        if (cancelled) return

        const viewer = viewerRef.current

        viewer.addEventListener('perspective-click', async (e) => {
          const detail = e.detail || {}
          const { row, column_names } = detail
          if (!row) return
          const eventFilters = (detail.config || {}).filter || []
          const config = await viewer.save()
          setClickDetail({ row, config, column_names, eventFilters })
          const matched = filterRowsByConfig(allRowsRef.current, eventFilters)
          setInspectedRows(matched)
        })

        await viewer.load(worker)

        const savedLayout = localStorage.getItem(LAYOUT_KEY(source))
        if (savedLayout) {
          await viewer.restore(JSON.parse(savedLayout))
        } else {
          await viewer.restore({ table: source, settings: true, plugin_config: DEFAULT_PLUGIN_CONFIG })
        }
        setStatus('ready')
      } catch (err) {
        if (!cancelled) { setStatus('error'); setError(err.message) }
      }
    }

    init()
    return () => { cancelled = true }
  }, [source])

  async function applyLayout(layout) {
    const viewer = viewerRef.current
    if (!viewer) return
    await viewer.restore(layout.config)
    setActiveLayoutId(layout.id)
    // also persist to localStorage so it survives refresh
    localStorage.setItem(LAYOUT_KEY(source), JSON.stringify(layout.config))
  }

  async function handleSaveAs() {
    const name = saveAsName.trim()
    if (!name) return
    const viewer = viewerRef.current
    if (!viewer) return
    const config = await viewer.save()
    try {
      const saved = await api.savePivotLayout(source, name, config)
      localStorage.setItem(LAYOUT_KEY(source), JSON.stringify(config))
      await loadLayouts()
      setActiveLayoutId(saved.id)
      setShowSaveAs(false)
      setSaveAsName('')
      flashMsg('Saved!')
    } catch (err) {
      flashMsg(err.message)
    }
  }

  async function handleDelete(layout, e) {
    e.stopPropagation()
    try {
      await api.deletePivotLayout(source, layout.id)
      if (activeLayoutId === layout.id) setActiveLayoutId(null)
      await loadLayouts()
      flashMsg('Deleted')
    } catch (err) {
      flashMsg(err.message)
    }
  }

  function handleResetToDefault() {
    const viewer = viewerRef.current
    if (!viewer) return
    localStorage.removeItem(LAYOUT_KEY(source))
    setActiveLayoutId(null)
    viewer.restore({ table: source, settings: true, plugin_config: DEFAULT_PLUGIN_CONFIG })
  }

  if (!source) return <div className="p-6 text-sm text-gray-400">Select a source first.</div>

  const cols = inspectedRows?.length ? Object.keys(inspectedRows[0]) : []

  const groupBy = clickDetail?.config?.group_by || []
  const splitBy = clickDetail?.config?.split_by || []
  const coordFields = new Set([...groupBy, ...splitBy])
  const coordMap = Object.fromEntries(
    (clickDetail?.eventFilters || [])
      .filter(([f, op]) => coordFields.has(f) && op === '==')
      .map(([f, , v]) => [f, v])
  )
  const cellCoords = [...groupBy, ...splitBy].map(f => coordMap[f]).filter(Boolean)
  const splitVals = splitBy.map(f => coordMap[f]).filter(Boolean)
  const metrics = clickDetail?.column_names || []
  const cellKey = splitVals.length > 0 && metrics.length > 0
    ? [...splitVals, ...metrics].join('|')
    : null

  return (
    <div className="w-full h-full flex flex-col">

      {/* Layout toolbar */}
      <div className="flex items-center gap-2 px-3 py-1.5 bg-white border-b border-gray-200 flex-shrink-0">
        <span className="text-xs text-gray-400 uppercase tracking-wide mr-1">Layouts</span>

        {layouts.map(l => (
          <div key={l.id}
            onClick={() => applyLayout(l)}
            className={`flex items-center gap-1 text-xs rounded px-2 py-0.5 cursor-pointer border transition-colors
              ${activeLayoutId === l.id
                ? 'bg-blue-50 border-blue-300 text-blue-700'
                : 'bg-white border-gray-200 text-gray-600 hover:border-gray-400'}`}>
            {l.layout_name}
            <button
              onClick={(e) => handleDelete(l, e)}
              className="text-gray-300 hover:text-red-400 leading-none ml-0.5 text-sm">×</button>
          </div>
        ))}

        {showSaveAs ? (
          <div className="flex items-center gap-1">
            <input
              autoFocus
              value={saveAsName}
              onChange={e => setSaveAsName(e.target.value)}
              onKeyDown={e => { if (e.key === 'Enter') handleSaveAs(); if (e.key === 'Escape') { setShowSaveAs(false); setSaveAsName('') } }}
              placeholder="Layout name…"
              className="text-xs border border-gray-300 rounded px-2 py-0.5 w-36 focus:outline-none focus:border-blue-400"
            />
            <button onClick={handleSaveAs} className="text-xs text-blue-600 hover:text-blue-800 px-1">Save</button>
            <button onClick={() => { setShowSaveAs(false); setSaveAsName('') }} className="text-xs text-gray-400 hover:text-gray-600 px-1">Cancel</button>
          </div>
        ) : (
          <button
            onClick={() => setShowSaveAs(true)}
            className="text-xs text-gray-400 hover:text-gray-600 border border-dashed border-gray-200 rounded px-2 py-0.5">
            + Save as…
          </button>
        )}

        {activeLayoutId !== null && (
          <button onClick={handleResetToDefault}
            className="text-xs text-gray-300 hover:text-gray-500 ml-1">
            reset
          </button>
        )}

        {layoutMsg && <span className="text-xs text-green-600 ml-1">{layoutMsg}</span>}

        {/* expand_depth test buttons */}
        <div className="ml-auto flex items-center gap-1">
          <span className="text-xs text-gray-300">expand test:</span>
          {[0, 1, 2, 3].map(d => (
            <button key={d} onClick={async () => {
              const v = viewerRef.current; if (!v) return
              const cfg = await v.save()
              const next = { ...cfg, plugin_config: { ...cfg.plugin_config, expand_depth: d } }
              console.log('trying plugin_config expand_depth:', d, next)
              await v.restore(next)
            }} className="text-xs border border-gray-200 rounded px-1.5 py-0.5 text-gray-500 hover:border-gray-400">
              {d}
            </button>
          ))}
        </div>
      </div>

      {/* Pivot + inspector */}
      <div className="relative flex-1 flex min-h-0">
        <div className="relative flex-1">
          {status === 'loading' && (
            <div className="absolute inset-0 flex items-center justify-center z-10 bg-gray-50">
              <p className="text-sm text-gray-400">Loading…</p>
            </div>
          )}
          {status === 'error' && (
            <div className="absolute inset-0 flex items-center justify-center z-10 bg-gray-50">
              <p className="text-sm text-red-500">Error: {error}</p>
            </div>
          )}
          {status === 'noview' && (
            <div className="absolute inset-0 flex items-center justify-center z-10 bg-gray-50">
              <p className="text-sm text-gray-400">No view data — generate a view and transform records first.</p>
            </div>
          )}
          <perspective-viewer
            ref={viewerRef}
            style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }}
          />
        </div>

        {inspectedRows && clickDetail && (
          <div className="w-80 border-l border-gray-200 bg-white flex flex-col overflow-hidden flex-shrink-0">
            <div className="flex items-center justify-between px-3 py-2 border-b border-gray-100">
              <span className="text-xs font-semibold text-gray-600 uppercase tracking-wide">
                {inspectedRows.length} row{inspectedRows.length !== 1 ? 's' : ''}
              </span>
              <button onClick={() => { setInspectedRows(null); setClickDetail(null) }}
                className="text-gray-300 hover:text-gray-500 leading-none text-lg">×</button>
            </div>
            <div className="flex-1 overflow-y-auto">

              {/* Cell coordinates */}
              <div className="px-3 py-2 border-b border-gray-100">
                <div className="text-xs text-gray-400 uppercase tracking-wide mb-1">
                  {[...groupBy, ...splitBy].join(' › ') || clickDetail.column_names?.join(', ') || 'Cell'}
                </div>
                {cellCoords.length > 0 && (
                  <div className="text-xs text-gray-700 font-mono font-semibold">
                    {cellCoords.join(' › ')}
                  </div>
                )}
                {Object.entries(clickDetail.row)
                  .filter(([k, v]) => k !== '__ROW_PATH__' && v != null)
                  .map(([k, v]) => {
                    const isSelected = cellKey != null && k === cellKey
                    return (
                      <div key={k} className={`flex justify-between py-0.5 gap-2 ${isSelected ? 'font-semibold' : ''}`}>
                        <span className={`text-xs font-mono shrink-0 ${isSelected ? 'text-gray-700' : 'text-gray-400'}`}>{k}</span>
                        <span className={`text-xs font-mono text-right ${isSelected ? 'text-blue-600' : 'text-gray-700'}`}>{formatVal(v)}</span>
                      </div>
                    )
                  })}
              </div>

              {/* User-set filters */}
              {(() => {
                const userFilters = (clickDetail.eventFilters || []).filter(([f]) => !coordFields.has(f))
                return userFilters.length > 0 ? (
                  <div className="px-3 py-2 border-b border-gray-100">
                    <div className="text-xs text-gray-400 uppercase tracking-wide mb-1">Filters</div>
                    {userFilters.map((f, i) => (
                      <div key={i} className="text-xs text-gray-500 py-0.5 font-mono">{f.join(' ')}</div>
                    ))}
                  </div>
                ) : null
              })()}

              {/* Underlying rows */}
              {inspectedRows.length > 0 && (
                <div className="overflow-auto">
                  <table className="w-full text-xs">
                    <thead>
                      <tr className="text-left text-gray-400 border-b border-gray-100 bg-gray-50 sticky top-0">
                        {cols.map(c => (
                          <th key={c} className="px-2 py-1 font-medium whitespace-nowrap">{c}</th>
                        ))}
                      </tr>
                    </thead>
                    <tbody>
                      {inspectedRows.map((row, i) => (
                        <tr key={i} className="border-t border-gray-50 hover:bg-gray-50">
                          {cols.map(c => {
                            const f = formatVal(row[c])
                            return (
                              <td key={c} className="px-2 py-1 font-mono whitespace-nowrap text-gray-700 max-w-40 truncate">
                                {f == null ? <span className="text-gray-300">—</span> : f}
                              </td>
                            )
                          })}
                        </tr>
                      ))}
                    </tbody>
                  </table>
                </div>
              )}
            </div>
          </div>
        )}
      </div>
    </div>
  )
}
