import { relative } from 'path'
import * as React from 'react'
import { Suspense, use, useMemo } from 'react'
import { Box, NoSelect, Text } from '../../../ink.js'
import type {
  NotebookCellType,
  NotebookContent,
} from '../../../types/notebook.js'
import { intersperse } from '../../../utils/array.js'
import { getCwd } from '../../../utils/cwd.js'
import { getPatchForDisplay } from '../../../utils/diff.js'
import { getFsImplementation } from '../../../utils/fsOperations.js'
import { safeParseJSON } from '../../../utils/json.js'
import { parseCellId } from '../../../utils/notebook.js'
import { HighlightedCode } from '../../HighlightedCode.js'
import { StructuredDiff } from '../../StructuredDiff.js'

type Props = {
  notebook_path: string
  cell_id: string | undefined
  new_source: string
  cell_type?: NotebookCellType
  edit_mode?: string
  verbose: boolean
  width: number
}

type InnerProps = {
  notebook_path: string
  cell_id: string | undefined
  new_source: string
  cell_type?: NotebookCellType
  edit_mode?: string
  verbose: boolean
  width: number
  promise: Promise<NotebookContent | null>
}

export function NotebookEditToolDiff(props: Props): React.ReactNode {
  // Create a promise that never rejects so we can handle errors inline.
  // Memoized on notebook_path so we don't re-read on every render.
  const notebookDataPromise = useMemo(
    () =>
      getFsImplementation()
        .readFile(props.notebook_path, { encoding: 'utf-8' })
        .then(content => safeParseJSON(content) as NotebookContent | null)
        .catch(() => null),
    [props.notebook_path],
  )

  return (
    <Suspense fallback={null}>
      <NotebookEditToolDiffInner {...props} promise={notebookDataPromise} />
    </Suspense>
  )
}

function NotebookEditToolDiffInner({
  notebook_path,
  cell_id,
  new_source,
  cell_type,
  edit_mode = 'replace',
  verbose,
  width,
  promise,
}: InnerProps): React.ReactNode {
  const notebookData = use(promise)

  const oldSource = useMemo(() => {
    if (!notebookData || !cell_id) {
      return ''
    }
    const cellIndex = parseCellId(cell_id)
    if (cellIndex !== undefined) {
      if (notebookData.cells[cellIndex]) {
        const source = notebookData.cells[cellIndex].source
        return Array.isArray(source) ? source.join('') : source
      }
      return ''
    }
    const cell = notebookData.cells.find(cell => cell.id === cell_id)
    if (!cell) {
      return ''
    }
    return Array.isArray(cell.source) ? cell.source.join('') : cell.source
  }, [notebookData, cell_id])

  const hunks = useMemo(() => {
    if (!notebookData || edit_mode === 'insert' || edit_mode === 'delete') {
      return null
    }
    // Create a "fake" file content with just the cell source
    // This allows us to use the regular diff mechanism
    return getPatchForDisplay({
      filePath: notebook_path,
      fileContents: oldSource,
      edits: [
        {
          old_string: oldSource,
          new_string: new_source,
          replace_all: false,
        },
      ],
      ignoreWhitespace: false,
    })
  }, [notebookData, notebook_path, oldSource, new_source, edit_mode])

  let editTypeDescription: string
  switch (edit_mode) {
    case 'insert':
      editTypeDescription = 'Insert new cell'
      break
    case 'delete':
      editTypeDescription = 'Delete cell'
      break
    default:
      editTypeDescription = 'Replace cell contents'
  }

  return (
    <Box flexDirection="column">
      <Box borderStyle="round" flexDirection="column" paddingX={1}>
        <Box paddingBottom={1} flexDirection="column">
          <Text bold>
            {verbose ? notebook_path : relative(getCwd(), notebook_path)}
          </Text>
          <Text dimColor>
            {editTypeDescription} for cell {cell_id}
            {cell_type ? ` (${cell_type})` : ''}
          </Text>
        </Box>
        {edit_mode === 'delete' ? (
          <Box flexDirection="column" paddingLeft={2}>
            <HighlightedCode code={oldSource} filePath={notebook_path} />
          </Box>
        ) : edit_mode === 'insert' ? (
          <Box flexDirection="column" paddingLeft={2}>
            <HighlightedCode
              code={new_source}
              filePath={cell_type === 'markdown' ? 'file.md' : notebook_path}
            />
          </Box>
        ) : hunks ? (
          intersperse(
            hunks.map(_ => (
              <StructuredDiff
                key={_.newStart}
                patch={_}
                dim={false}
                width={width}
                filePath={notebook_path}
                firstLine={new_source.split('\n')[0] ?? null}
                fileContent={oldSource}
              />
            )),
            i => (
              <NoSelect fromLeftEdge key={`ellipsis-${i}`}>
                <Text dimColor>...</Text>
              </NoSelect>
            ),
          )
        ) : (
          <HighlightedCode
            code={new_source}
            filePath={cell_type === 'markdown' ? 'file.md' : notebook_path}
          />
        )}
      </Box>
    </Box>
  )
}
