// Top-level app — real API calls to /api/files/*. SNB called directly from browser via VPN.
// Config persisted in localStorage under "pv-config".

// Browser-side SNB token cache. Credentials come from server /api/snb/config (env vars).
// All SNB calls (auth + data) go directly from browser via user's VPN.
let _snbToken  = null
let _snbConfig = null  // { host, username, password } fetched once from server

async function getSnbConfig() {
  if (_snbConfig) return _snbConfig
  const r = await fetch('/api/snb/config')
  if (!r.ok) {
    const d = await r.json().catch(() => ({}))
    throw new Error(d.error || 'SNB not configured on server')
  }
  _snbConfig = await r.json()
  return _snbConfig
}

async function acquireSnbToken() {
  const cfg = await getSnbConfig()
  const r = await fetch(`${cfg.host}/sky-auth/v1/user/authenticate`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: cfg.username, password: cfg.password }),
  })
  if (!r.ok) throw new Error(`SNB auth failed (${r.status}) — connect VPN first`)
  const data = await r.json()
  return data.access_token
}

async function callSnbDirect(type, params) {
  const cfg = await getSnbConfig()
  const path = resolveSnbPath(type, params)
  if (!_snbToken) _snbToken = await acquireSnbToken()
  let r = await fetch(`${cfg.host}${path}`, { headers: { Authorization: `Bearer ${_snbToken}` } })
  if (r.status === 401) {
    _snbToken = null
    _snbToken = await acquireSnbToken()
    r = await fetch(`${cfg.host}${path}`, { headers: { Authorization: `Bearer ${_snbToken}` } })
  }
  if (!r.ok) throw new Error(`SNB error ${r.status}`)
  return r.json()
}

function App() {
  const [view, setView]           = React.useState('table')
  const [graphDir, setGraphDir]   = React.useState('LR')
  const [source, setSource]       = React.useState('FILE')
  const [type, setType]           = React.useState('SET')
  const [query, setQuery]         = React.useState('')
  const [param, setParam]         = React.useState('productOfferingName')
  const [loading, setLoading]     = React.useState(false)
  const [error, setError]         = React.useState(null)

  const [mode, setMode]               = React.useState('empty')
  const [results, setResults]         = React.useState([])
  const [resultsPage, setResultsPage] = React.useState(1)
  const [totalPages, setTotalPages]   = React.useState(1)
  const [totalCount, setTotalCount]   = React.useState(0)
  const [sortBy, setSortBy]           = React.useState('id')
  const [sortDir, setSortDir]         = React.useState('asc')
  const [selectedFile, setSelectedFile] = React.useState(null)

  const [duration, setDuration]       = React.useState(null)
  const [cache, setCache]             = React.useState('miss')
  const [relatedFiles, setRelatedFiles]   = React.useState(null)   // { [id]: content } flat map
  const [loadingNodes, setLoadingNodes]   = React.useState(new Set())
  const [showConfig, setShowConfig]   = React.useState(false)
  const [historyOpen, setHistoryOpen] = React.useState(false)
  const [history, setHistory]         = React.useState([])
  const [toast, setToast]             = React.useState(null)

  const [config, setConfig]           = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('pv-config') || 'null') || DEFAULT_CONFIG }
    catch { return DEFAULT_CONFIG }
  })
  const [configVersion, setConfigVersion] = React.useState(0)

  const configReady = Boolean(config.activityRepo && config.commonRepo)

  // Show config modal on first load if repos are not set
  React.useEffect(() => {
    if (!configReady) setShowConfig(true)
  }, [])

  // Auto-load directory listing whenever source=FILE + config is ready or config changes
  React.useEffect(() => {
    if (source === 'FILE' && configReady) loadFileList(1)
  }, [source, configReady, configVersion])

  // Clear related files when navigating to a different file
  React.useEffect(() => { setRelatedFiles(null); setLoadingNodes(new Set()) }, [selectedFile])

  function pushHistory(file, src) {
    setHistory(prev => {
      const without = prev.filter(h => !(h.id === file.id && h.source === src))
      return [{ ...file, source: src, openedAt: Date.now() }, ...without].slice(0, 20)
    })
  }

  function flash(msg) { setToast(msg); setTimeout(() => setToast(null), 1800) }

  function buildQs(extra = {}) {
    return new URLSearchParams({
      activityRepo:   config.activityRepo,
      activityBranch: config.activityBranch,
      commonRepo:     config.commonRepo,
      commonBranch:   config.commonBranch,
      type,
      ...extra,
    }).toString()
  }

  async function loadFileList(page = 1, nocache = false, sb = sortBy, sd = sortDir, exitDetail = false) {
    if (!configReady) return
    setLoading(true)
    setError(null)
    const t0 = Date.now()
    try {
      const extra = { query: query.trim(), page, per_page: 10, sortBy: sb, sortDir: sd }
      if (nocache) extra.nocache = '1'
      const r = await fetch(`/api/files/list?${buildQs(extra)}`)
      const data = await r.json()
      if (!r.ok) throw new Error(data.error || r.statusText)
      setResults(data.items)
      setTotalCount(data.total_count)
      setTotalPages(data.total_pages)
      setResultsPage(data.page)
      if (exitDetail) { setSelectedFile(null); setMode('results') }
      else setMode(m => m === 'detail' ? m : 'results')
      setDuration(Date.now() - t0)
      setCache('hit')
    } catch (err) {
      setError(err.message)
      setMode('empty')
      flash('Error: ' + err.message)
    } finally {
      setLoading(false)
    }
  }

  function handlePageChange(newPage) { loadFileList(newPage) }

  function handleSortChange(col) {
    const nextDir = col === sortBy && sortDir === 'asc' ? 'desc' : 'asc'
    setSortBy(col)
    setSortDir(nextDir)
    loadFileList(1, false, col, nextDir)
  }

  async function doSearch() {
    if (source === 'FILE') {
      if (mode === 'detail' && query.trim()) {
        await openFile({ id: query.trim() })
        return
      }
      await loadFileList(1, false, sortBy, sortDir, true)
    } else {
      if (!query.trim()) { flash('Enter an ID or name.'); return }
      setLoading(true)
      setError(null)
      const t0 = Date.now()
      try {
        const data = await callSnbDirect(type, { id: query, name: query, param, action: '' })
        const snbFile = {
          id:      data.id || query,
          name:    data.name || query,
          path:    `/snb/${type}/${query}`,
          repo:    'snb',
          content: data,
        }
        setSelectedFile(snbFile)
        setMode('detail')
        setDuration(Date.now() - t0)
        setCache('hit')
        pushHistory(snbFile, 'SNB')
      } catch (err) {
        _snbToken = null
        setError(err.message)
        setMode('empty')
        flash('SNB error: ' + err.message)
      } finally {
        setLoading(false)
      }
    }
  }

  async function openFile(file) {
    setLoading(true)
    setError(null)
    const t0 = Date.now()
    try {
      const r = await fetch(`/api/files/fetch?${buildQs({ id: file.id })}`)
      const data = await r.json()
      if (!r.ok) throw new Error(data.error || r.statusText)
      const loaded = {
        ...file,
        content: data.content,
        path:    data.path,
        repo:    data.repo,
        sha:     data.sha,
        name:    data.content?.name || data.content?.id || file.id,
      }
      setSelectedFile(loaded)
      setMode('detail')
      setDuration(Date.now() - t0)
      setCache('hit')
      pushHistory(loaded, source)
    } catch (err) {
      setError(err.message)
      flash('Error: ' + err.message)
    } finally {
      setLoading(false)
    }
  }

  function backToResults() {
    setMode(source === 'FILE' ? 'results' : 'empty')
    setSelectedFile(null)
    setView('table')
  }

  function doReset() {
    setQuery('')
    setSelectedFile(null)
    setView('table')
    setResultsPage(1)
    if (source === 'FILE' && configReady) loadFileList(1)
    else setMode('empty')
    flash('Reset.')
  }

  async function doRefresh() {
    setCache('miss')
    if (mode === 'detail' && selectedFile && source === 'FILE') {
      await openFile(selectedFile)
    } else if (source === 'FILE') {
      await loadFileList(resultsPage, true)
    } else {
      await doSearch()
    }
  }

  function doClearCache() { setCache('miss'); flash('Cache cleared.') }

  async function loadNode(id, fileType) {
    if (!id) return
    setLoadingNodes(prev => new Set([...prev, id]))
    try {
      const r = await fetch(`/api/files/fetch?${buildQs({ id, type: fileType })}`)
      const data = await r.json()
      if (!r.ok) throw new Error(data.error || r.statusText)
      setRelatedFiles(prev => ({ ...(prev || {}), [id]: data.content }))
    } catch (err) {
      flash('Error: ' + err.message)
    } finally {
      setLoadingNodes(prev => { const s = new Set(prev); s.delete(id); return s })
    }
  }

  function hideNode(id) {
    setRelatedFiles(prev => {
      if (!prev) return null
      const next = { ...prev }
      delete next[id]
      return Object.keys(next).length ? next : null
    })
  }

  // Called by ConfigModal on every complete-draft change (auto-save, no close)
  function saveConfig(next) {
    setConfig(next)
    localStorage.setItem('pv-config', JSON.stringify(next))
    setConfigVersion(v => v + 1)
  }

  function switchSource(s) {
    if (s === source) return
    setSource(s)
    setType(s === 'FILE' ? 'SET' : 'policyRule')
    setMode('empty')
    setSelectedFile(null)
    setResults([])
    setView('table')
  }

  function openFromHistory(item) {
    if (item.source !== source) {
      setSource(item.source)
      setType(item.source === 'FILE' ? 'RULE' : 'policyRule')
    }
    setSelectedFile(item)
    setMode('detail')
    setHistoryOpen(false)
    pushHistory(item, item.source)
  }

  // Status text
  let statusText = 'Ready.'
  if (loading) {
    statusText = 'Loading…'
  } else if (mode === 'results') {
    statusText = query
      ? `${totalCount} file${totalCount !== 1 ? 's' : ''} matched · ${duration}ms`
      : `${totalCount} file${totalCount !== 1 ? 's' : ''} available · ${duration}ms`
  } else if (mode === 'detail' && selectedFile) {
    statusText = ''
  }

  const isDetail = mode === 'detail'

  // Body content
  let body
  if (loading) {
    body = (
      <div className="pv-loading">
        <div className="pv-spinner" /><span>Loading…</span>
      </div>
    )
  } else if (mode === 'empty') {
    body = (
      <div className="pv-empty" data-screen-label="Empty">
        {!configReady ? (
          <>
            <div>No repos configured.</div>
            <div className="hint">
              <button className="pv-mini-btn" style={{ fontSize: 13 }} onClick={() => setShowConfig(true)}>
                Open Config →
              </button>
            </div>
          </>
        ) : (
          <>
            <div>{source === 'FILE' ? 'Search across activity + common repos.' : 'Enter an ID to load from SNB.'}</div>
            <div className="hint">{source === 'FILE' ? 'Try a partial ID or keyword.' : 'e.g. PLRL230316001'}</div>
          </>
        )}
        {error && <div style={{ color: 'var(--danger-600)', fontSize: 12, marginTop: 8 }}>{error}</div>}
      </div>
    )
  } else if (mode === 'results') {
    body = (
      <ResultsList
        results={results}
        query={query}
        onOpen={openFile}
        loading={false}
        page={resultsPage}
        totalPages={totalPages}
        totalCount={totalCount}
        onPageChange={handlePageChange}
        fileType={type}
        sortBy={sortBy}
        sortDir={sortDir}
        onSortChange={handleSortChange}
      />
    )
  } else {
    body = (
      <div className="pv-detail-body">
        <div className="pv-breadcrumb">
          <button className="pv-breadcrumb-back" onClick={backToResults}>‹ Back</button>
          <span className="pv-breadcrumb-sep">/</span>
          <span className="pv-breadcrumb-id">{selectedFile?.id}</span>
          {selectedFile?.name !== selectedFile?.id && (
            <span className="pv-breadcrumb-name">{selectedFile?.name}</span>
          )}
          <span className="pv-breadcrumb-spacer" />
          <span className="pv-breadcrumb-meta"><code>{selectedFile?.path}</code></span>
        </div>
        {view === 'table'
          ? <TableView json={selectedFile?.content} loading={false} query="" />
          : <GraphView json={selectedFile?.content} loading={false} direction={graphDir} onDirectionChange={setGraphDir}
              fileType={type} relatedFiles={relatedFiles} loadingNodes={loadingNodes}
              onLoadNode={loadNode} onHideNode={hideNode} />
        }
      </div>
    )
  }

  return (
    <>
      <Header
        onOpenConfig={() => setShowConfig(true)}
        onToggleHistory={() => setHistoryOpen(o => !o)}
        historyCount={history.length}
        historyOpen={historyOpen}
      />

      <main className="pv-main">
        <section className="pv-card">
          <SearchBar
            source={source} onSourceChange={switchSource}
            type={type} onTypeChange={setType}
            query={query} onQueryChange={setQuery}
            onSearch={doSearch} onReset={doReset}
            loading={loading}
            cache={cache} onRefresh={doRefresh} onClearCache={doClearCache}
            param={param} onParamChange={setParam}
            statusText={statusText}
            detailMode={isDetail}
            view={view} onView={setView}
            rawJson={selectedFile?.content || null}
            metadata={isDetail ? {
              source,
              type,
              url:        selectedFile?.path,
              repo:       selectedFile?.repo,
              sha:        selectedFile?.sha,
              cacheKey:   `${source}|${type}|${selectedFile?.id}`,
              durationMs: duration,
            } : null}
          />
          <div className="pv-card-body">
            {body}
          </div>
        </section>
      </main>

      <ConfigModal
        open={showConfig}
        onClose={() => setShowConfig(false)}
        config={config}
        onSave={saveConfig}
      />

      <HistorySidebar
        open={historyOpen}
        onClose={() => setHistoryOpen(false)}
        items={history}
        onOpen={openFromHistory}
        onClear={() => { setHistory([]); flash('History cleared.') }}
      />

      {toast && <div className="pv-toast">{toast}</div>}
    </>
  )
}

ReactDOM.createRoot(document.getElementById('root')).render(<App />)
