import { useMemo } from 'react'
import { Pressable, Text, View } from 'react-native'
import { MobileNativeChatOwnedScrollView } from './MobileNativeChatOwnedScrollView'
import { ChevronRight, FileMinus2, FilePen, FilePlus2 } from 'lucide-react-native'
import {
NATIVE_CHAT_DIFF_CARD_COPY,
nativeChatEditFilePatchText
} from '../../../src/shared/native-chat/tools/native-chat-edit-cards'
import {
unifiedLineNumber,
type NativeChatEditFile,
type NativeChatEditLine
} from '../../../src/shared/native-chat/tools/native-chat-edit-model'
import { NATIVE_CHAT_TOOL_ROW_VERB_COPY } from '../../../src/shared/native-chat/tools/native-chat-tool-line-label'
import { MobileSourceCopyButton } from '../components/MobileSourceCopyButton'
import { colors } from '../theme/mobile-theme'
import { useMobileNativeChatOpenedDetail } from './mobile-native-chat-opened-detail'
import { useMobileNativeChatDisclosure } from './mobile-native-chat-disclosure-store'
import { toolStyles as styles, toolType } from './mobile-native-chat-tool-styles'
function baseName(path: string): string {
return path.split(/[\\/]/).at(-1) || path
}
function VerbIcon({ kind, size }: { kind: NativeChatEditFile['changeKind']; size: number }) {
const Icon = kind === 'added' ? FilePlus2 : kind === 'deleted' ? FileMinus2 : FilePen
return
}
function DiffRow({
line,
gutterChars,
type
}: {
line: NativeChatEditLine
gutterChars: number
type: ReturnType['code']
}): React.JSX.Element {
if (line.kind === 'gap') {
return (
⋯
)
}
return (
{gutterChars > 0 ? (
{unifiedLineNumber(line) ?? ''}
) : null}
{line.kind === 'add' ? '+' : line.kind === 'del' ? '-' : ' '}
{line.text}
)
}
// Mount rows only when expanded so collapsed cards avoid diff work.
function DiffCardRows({
file,
fontScale,
disclosureKey,
rootIdentity
}: {
file: NativeChatEditFile
fontScale: number
disclosureKey?: string
rootIdentity?: string
}) {
const onLayout = useMobileNativeChatOpenedDetail()
const type = toolType(fontScale).code
// Leave unresolved gutters blank; snippet-relative numbers would imply real file positions.
const widest = file.lineNumbersKnown
? file.lines.reduce((max, line) => Math.max(max, unifiedLineNumber(line) ?? 0), 0)
: 0
const gutterChars = file.lineNumbersKnown ? Math.max(3, String(widest).length + 1) : 0
const seen = new Map()
return (
{file.lines.map((line) => {
const signature = `${line.kind}:${line.oldLineNumber}:${line.newLineNumber}:${line.text}`
const occurrence = seen.get(signature) ?? 0
seen.set(signature, occurrence + 1)
return (
)
})}
)
}
export function MobileNativeChatDiffCard({
file,
fontScale,
disclosureKey,
rootIdentity,
onOpenFile
}: {
file: NativeChatEditFile
fontScale: number
disclosureKey?: string
rootIdentity?: string
onOpenFile?: (path: string) => void
}): React.JSX.Element {
const [expanded, setExpanded] = useMobileNativeChatDisclosure(disclosureKey, false)
const hasBody = file.lines.length > 0
const copyText = useMemo(() => nativeChatEditFilePatchText(file.lines), [file.lines])
const type = toolType(fontScale)
const iconSize = 15 * fontScale
const openFile = file.changeKind === 'deleted' ? undefined : onOpenFile
return (
hasBody && setExpanded(!expanded)}
disabled={!hasBody}
accessibilityRole="button"
accessibilityState={hasBody ? { expanded } : undefined}
>
{NATIVE_CHAT_TOOL_ROW_VERB_COPY[file.changeKind]}
{file.oldPath ? (
<>
{baseName(file.oldPath)}
→
>
) : null}
openFile(file.path) : undefined}
suppressHighlighting={!openFile}
accessibilityRole={openFile ? 'link' : undefined}
>
{baseName(file.path)}
{file.added > 0 ? {`+${file.added}`} : null}
{file.added > 0 && file.removed > 0 ? ' ' : null}
{file.removed > 0 ? (
{`-${file.removed}`}
) : null}
{file.truncated ? (
{NATIVE_CHAT_DIFF_CARD_COPY.diffTruncated}
) : null}
{hasBody ? (
) : null}
{hasBody && expanded ? (
) : null}
)
}