import React, { useEffect, useMemo, useRef, useState } from 'react';
import { themedColor } from '../../theme';
import {
  ActivityIndicator,
  Image,
  Modal,
  Pressable,
  ScrollView,
  Text,
  TextInput,
  View,
} from 'react-native';
import {
  AlertCircle,
  ChevronDown,
  ChevronRight,
  File,
  FileCode,
  FileImage,
  FileText,
  Folder,
  Save,
  Search,
  Upload,
  X,
  type LucideIcon,
} from 'lucide-react-native';

import {
  DEFAULT_SANDBOX_FILE_PATHS,
  buildFileTree,
  flattenFileTree,
  getFileCategory,
  getFileName,
  getFolderPath,
  getImageMimeType,
  isEditableFile,
} from './fileTreeUtils';
import { editorShellStyles } from './editorShellStyles';
import { useAgentBi } from '../../analytics/mixpanelContext';
import { useSuperagentFiles } from '../../runtime/runtimeContext';
import { styles } from '../../styles';
import type {
  SuperagentAgent,
  SuperagentSandboxFileContent,
  SuperagentSandboxFileNode,
} from '../../types';

export function FilesPanel({ agent }: { agent: SuperagentAgent }) {
  const {
    fileLoadError,
    fileLoadFailed,
    filePaths = [],
    isLoadingFiles: isLoading,
    onOpenSandboxFile,
    onSaveSandboxFile,
    onUploadSandboxFiles,
  } = useSuperagentFiles();
  const bi = useAgentBi();
  const [query, setQuery] = useState('');
  const [openedFile, setOpenedFile] = useState<SuperagentSandboxFileContent | null>(null);
  const [draftContent, setDraftContent] = useState('');
  const [fileError, setFileError] = useState<string | null>(null);
  const [loadingPath, setLoadingPath] = useState<string | null>(null);
  const [isSaving, setIsSaving] = useState(false);
  const [isUploading, setIsUploading] = useState(false);
  const [uploadStatus, setUploadStatus] = useState<string | null>(null);
  const { fileCount, folderCount, tree } = useMemo(() => {
    const primaryTree = buildFileTree(filePaths);
    if (primaryTree.fileCount > 0 || primaryTree.folderCount > 0 || primaryTree.tree.length > 0) {
      return primaryTree;
    }

    return buildFileTree(DEFAULT_SANDBOX_FILE_PATHS);
  }, [filePaths]);
  const allFiles = useMemo(() => flattenFileTree(tree), [tree]);
  const filteredFiles = useMemo(() => {
    const normalizedQuery = query.trim().toLowerCase();
    if (!normalizedQuery) {
      return [];
    }

    return allFiles.filter((path) => path.toLowerCase().includes(normalizedQuery));
  }, [allFiles, query]);

  // Fire once per drawer open, after the load completes — never with the fallback
  // scaffold's counts (skip while loading or when the API list failed), and using
  // the real filePaths counts rather than the displayed scaffold.
  const viewedRef = useRef(false);
  useEffect(() => {
    if (viewedRef.current || isLoading || fileLoadFailed) return;
    viewedRef.current = true;
    const loaded = buildFileTree(filePaths);
    void bi.trackEditor('Files Viewed', { file_count: loaded.fileCount, folder_count: loaded.folderCount });
  }, [isLoading, fileLoadFailed, filePaths, bi]);

  // Report search on the settled query, not per keystroke, so search-volume and
  // zero-result counts aren't inflated. Native search is flat, so folder_depth is 0.
  // Keyed on `query` alone (latest count read via a ref) so a file-list reload that
  // changes results_count for the same query text doesn't re-fire the event.
  const filteredCountRef = useRef(0);
  filteredCountRef.current = filteredFiles.length;
  useEffect(() => {
    const normalizedQuery = query.trim();
    if (!normalizedQuery) return;
    const timer = setTimeout(() => {
      void bi.trackEditor('Files Search', {
        query_length: normalizedQuery.length,
        results_count: filteredCountRef.current,
        folder_depth: 0,
      });
    }, 400);
    return () => clearTimeout(timer);
  }, [query, bi]);

  const openFile = async (path: string) => {
    if (!onOpenSandboxFile) {
      setFileError('File opening is not connected in this app build.');
      return;
    }

    void bi.trackEditor('File Open', { file_name: path });

    // Binary files (non-SVG images, PDFs) can't be read through the text-only
    // sandbox content endpoint — it returns 415 for non-UTF-8 bytes — so don't
    // fetch them. Open a placeholder instead of surfacing a confusing error;
    // a real preview can return once a binary-safe read/download endpoint exists.
    const category = getFileCategory(path);
    const isBinaryUnreadable = (category === 'image' && getImageMimeType(path) !== 'image/svg+xml') || category === 'pdf';
    if (isBinaryUnreadable) {
      setFileError(null);
      setOpenedFile({ content: '', path });
      setDraftContent('');
      return;
    }

    setLoadingPath(path);
    setFileError(null);
    try {
      const file = await onOpenSandboxFile({ agentId: agent.id, path });
      setOpenedFile(file);
      setDraftContent(file.content);
    } catch (error) {
      setFileError(error instanceof Error ? error.message : 'Failed to open file.');
    } finally {
      setLoadingPath(null);
    }
  };

  const uploadFiles = async () => {
    if (!onUploadSandboxFiles) {
      setFileError('Native sandbox upload is not connected in this app build.');
      return;
    }

    void bi.trackEditor('File Upload');
    setIsUploading(true);
    setFileError(null);
    setUploadStatus(null);
    try {
      const result = await onUploadSandboxFiles({ agentId: agent.id });
      const uploadedCount = Array.isArray(result) ? result.length : 0;
      setUploadStatus(uploadedCount > 0 ? `Uploaded ${uploadedCount} file${uploadedCount === 1 ? '' : 's'}.` : null);
    } catch (error) {
      setFileError(error instanceof Error ? error.message : 'Failed to upload files.');
    } finally {
      setIsUploading(false);
    }
  };

  const closeOpenedFile = () => {
    setOpenedFile(null);
    setDraftContent('');
  };

  const saveOpenedFile = async () => {
    if (!openedFile || !onSaveSandboxFile || !isEditableFile(openedFile.path)) {
      return;
    }

    setIsSaving(true);
    setFileError(null);
    try {
      await onSaveSandboxFile({
        agentId: agent.id,
        content: draftContent,
        path: openedFile.path,
      });
      setOpenedFile({ content: draftContent, path: openedFile.path });
      void bi.trackEditor('Library File Save', { redeploy: false }); // native has no redeploy toggle
    } catch (error) {
      setFileError(error instanceof Error ? error.message : 'Failed to save file.');
    } finally {
      setIsSaving(false);
    }
  };

  return (
    <View style={editorShellStyles.settingsStack}>
      <View style={editorShellStyles.panel}>
        <View style={editorShellStyles.panelHeader}>
          <View>
            <Text style={editorShellStyles.panelTitle}>Sandbox files</Text>
            <Text style={editorShellStyles.panelSubtitle}>
              {fileCount} files · {folderCount} folders
            </Text>
          </View>
          <Pressable
            accessibilityLabel="Upload files"
            accessibilityRole="button"
            disabled={isUploading}
            onPress={uploadFiles}
            style={({ pressed }) => [
              editorShellStyles.fileIconButton,
              pressed && styles.pressed,
              isUploading && editorShellStyles.disabledAction,
            ]}
          >
            {isUploading ? (
              <ActivityIndicator color={themedColor('#F4F4F5')} size="small" />
            ) : (
              <Upload color={themedColor('#F4F4F5')} size={16} strokeWidth={2.4} />
            )}
          </Pressable>
        </View>

        <View style={editorShellStyles.fileSearchWrap}>
          <Search color={themedColor('#A1A1AA')} size={16} strokeWidth={2.4} />
          <TextInput
            autoCapitalize="none"
            autoCorrect={false}
            clearButtonMode="while-editing"
            onChangeText={setQuery}
            placeholder="Search files"
            placeholderTextColor={themedColor('#8E8E93')}
            returnKeyType="search"
            style={editorShellStyles.fileSearchInput}
            value={query}
          />
        </View>

        {fileError ? <Text style={editorShellStyles.fileStatusError}>{fileError}</Text> : null}
        {uploadStatus ? <Text style={editorShellStyles.fileStatusText}>{uploadStatus}</Text> : null}
        {fileLoadFailed ? (
          <View style={editorShellStyles.fileStatusBanner}>
            <AlertCircle color={themedColor('#FBBF24')} size={15} strokeWidth={2.4} />
            <Text style={editorShellStyles.fileStatusBannerText}>
              Files API did not return a list. Showing the default sandbox folders.
            </Text>
          </View>
        ) : null}
        {fileLoadFailed && fileLoadError ? (
          <Text style={editorShellStyles.fileStatusError}>{fileLoadError}</Text>
        ) : null}

        {isLoading ? (
          <View style={editorShellStyles.loadingPanel}>
            <ActivityIndicator color={themedColor('#FF5A1F')} />
            <Text style={editorShellStyles.loadingText}>Loading files...</Text>
          </View>
        ) : fileCount === 0 && folderCount === 0 && tree.length === 0 ? (
          <View style={editorShellStyles.emptyPanel}>
            <Folder color={themedColor('#8E8E93')} size={22} strokeWidth={2.2} />
            <Text style={editorShellStyles.emptyTitle}>No files yet</Text>
            <Text style={editorShellStyles.emptyBody}>Generated and uploaded sandbox files will appear here.</Text>
          </View>
        ) : (
          <View style={editorShellStyles.fileTreeWrap}>
            {query.trim() ? (
              filteredFiles.length > 0 ? (
                filteredFiles.map((path) => (
                  <FileSearchRow
                    isLoading={loadingPath === path}
                    key={path}
                    onPress={() => openFile(path)}
                    path={path}
                  />
                ))
              ) : (
                <View style={editorShellStyles.emptyPanel}>
                  <Search color={themedColor('#8E8E93')} size={22} strokeWidth={2.2} />
                  <Text style={editorShellStyles.emptyTitle}>No matching files</Text>
                  <Text style={editorShellStyles.emptyBody}>Try another folder or file name.</Text>
                </View>
              )
            ) : (
              tree.map((node) => (
                <FileTreeNode
                  isLoadingPath={loadingPath}
                  key={node.name}
                  node={node}
                  onFilePress={openFile}
                />
              ))
            )}
          </View>
        )}
      </View>

      <FileEditorModal
        draftContent={draftContent}
        file={openedFile}
        isSaving={isSaving}
        onChangeDraft={setDraftContent}
        onClose={closeOpenedFile}
        onSave={saveOpenedFile}
      />
    </View>
  );
}

function FileTreeNode({
  depth = 0,
  isLoadingPath,
  node,
  onFilePress,
  parentPath = '',
}: {
  depth?: number;
  isLoadingPath: string | null;
  node: SuperagentSandboxFileNode;
  onFilePress: (path: string) => void;
  parentPath?: string;
}) {
  const bi = useAgentBi();
  const [expanded, setExpanded] = useState(depth < 1);
  const isFolder = node.type === 'folder';
  const path = parentPath ? `${parentPath}/${node.name}` : node.name;
  const toggleExpanded = () => {
    if (!expanded) void bi.trackEditor('Files Folder Open', { file_count: (node.children ?? []).filter((child) => child.type === 'file').length });
    setExpanded((current) => !current);
  };

  if (!isFolder) {
    return (
      <FileTreeRow
        depth={depth}
        isLoading={isLoadingPath === path}
        onPress={() => onFilePress(path)}
        path={path}
      />
    );
  }

  return (
    <View>
      <Pressable
        accessibilityRole="button"
        onPress={toggleExpanded}
        style={({ pressed }) => [
          editorShellStyles.fileTreeRow,
          { paddingLeft: 10 + depth * 14 },
          pressed && styles.pressed,
        ]}
      >
        {expanded ? (
          <ChevronDown color={themedColor('#8E8E93')} size={16} strokeWidth={2.5} />
        ) : (
          <ChevronRight color={themedColor('#8E8E93')} size={16} strokeWidth={2.5} />
        )}
        <Folder color={themedColor('#F59E0B')} size={17} strokeWidth={2.3} />
        <Text numberOfLines={1} style={editorShellStyles.fileFolderName}>{node.name}</Text>
        <Text style={editorShellStyles.fileFolderCount}>{flattenFileTree(node.children ?? []).length}</Text>
      </Pressable>
      {expanded ? (
        node.children?.map((child) => (
          <FileTreeNode
            depth={depth + 1}
            isLoadingPath={isLoadingPath}
            key={child.name}
            node={child}
            onFilePress={onFilePress}
            parentPath={path}
          />
        ))
      ) : null}
    </View>
  );
}

function FileSearchRow({
  isLoading,
  onPress,
  path,
}: {
  isLoading: boolean;
  onPress: () => void;
  path: string;
}) {
  return (
    <Pressable
      accessibilityRole="button"
      onPress={onPress}
      style={({ pressed }) => [
        editorShellStyles.fileSearchRow,
        pressed && styles.pressed,
      ]}
    >
      <FileKindIcon path={path} />
      <View style={editorShellStyles.fileRowText}>
        <Text numberOfLines={1} style={editorShellStyles.fileName}>{getFileName(path)}</Text>
        <Text numberOfLines={1} style={editorShellStyles.filePath}>{getFolderPath(path) || 'root'}</Text>
      </View>
      {isLoading ? <ActivityIndicator color={themedColor('#F4F4F5')} size="small" /> : null}
    </Pressable>
  );
}

function FileTreeRow({
  depth,
  isLoading,
  onPress,
  path,
}: {
  depth: number;
  isLoading: boolean;
  onPress: () => void;
  path: string;
}) {
  return (
    <Pressable
      accessibilityRole="button"
      onPress={onPress}
      style={({ pressed }) => [
        editorShellStyles.fileTreeRow,
        { paddingLeft: 32 + depth * 14 },
        pressed && styles.pressed,
      ]}
    >
      <FileKindIcon path={path} />
      <Text numberOfLines={1} style={editorShellStyles.fileName}>{getFileName(path)}</Text>
      {isLoading ? <ActivityIndicator color={themedColor('#F4F4F5')} size="small" /> : null}
    </Pressable>
  );
}

function FileKindIcon({ path }: { path: string }) {
  const category = getFileCategory(path);
  const Icon: LucideIcon = category === 'image'
    ? FileImage
    : category === 'code' || category === 'html'
      ? FileCode
      : category === 'markdown' || category === 'text'
        ? FileText
        : File;

  return <Icon color={category === 'image' ? themedColor('#67E8F9') : themedColor('#A1A1AA')} size={16} strokeWidth={2.3} />;
}

function FileEditorModal({
  draftContent,
  file,
  isSaving,
  onChangeDraft,
  onClose,
  onSave,
}: {
  draftContent: string;
  file: SuperagentSandboxFileContent | null;
  isSaving: boolean;
  onChangeDraft: (content: string) => void;
  onClose: () => void;
  onSave: () => void;
}) {
  if (!file) {
    return null;
  }

  const editable = isEditableFile(file.path);
  const isDirty = draftContent !== file.content;
  const category = getFileCategory(file.path);

  return (
    <Modal animationType="slide" presentationStyle="pageSheet" visible={!!file} onRequestClose={onClose}>
      <View style={editorShellStyles.fileModalRoot}>
        <View style={editorShellStyles.fileModalHeader}>
          <Pressable
            accessibilityLabel="Close file"
            accessibilityRole="button"
            onPress={onClose}
            style={({ pressed }) => [editorShellStyles.fileIconButton, pressed && styles.pressed]}
          >
            <X color={themedColor('#F4F4F5')} size={18} strokeWidth={2.5} />
          </Pressable>
          <View style={editorShellStyles.fileModalTitleWrap}>
            <Text numberOfLines={1} style={editorShellStyles.fileModalTitle}>{getFileName(file.path)}</Text>
            <Text numberOfLines={1} style={editorShellStyles.fileModalPath}>{file.path}</Text>
          </View>
          {editable ? (
            <Pressable
              accessibilityLabel="Save file"
              accessibilityRole="button"
              disabled={!isDirty || isSaving}
              onPress={onSave}
              style={({ pressed }) => [
                editorShellStyles.fileSaveButton,
                (!isDirty || isSaving) && editorShellStyles.disabledAction,
                pressed && styles.pressed,
              ]}
            >
              {isSaving ? (
                <ActivityIndicator color={themedColor('#111111')} size="small" />
              ) : (
                <Save color={themedColor('#111111')} size={16} strokeWidth={2.5} />
              )}
              <Text style={editorShellStyles.fileSaveText}>Save</Text>
            </Pressable>
          ) : (
            <View style={editorShellStyles.fileReadOnlyBadge}>
              <Text style={editorShellStyles.fileReadOnlyText}>Read only</Text>
            </View>
          )}
        </View>
        <View style={editorShellStyles.fileEditorBody}>
          <FileContentView
            category={category}
            content={draftContent}
            editable={editable}
            onChangeText={onChangeDraft}
            path={file.path}
          />
        </View>
      </View>
    </Modal>
  );
}

function FileContentView({
  category,
  content,
  editable,
  onChangeText,
  path,
}: {
  category: ReturnType<typeof getFileCategory>;
  content: string;
  editable: boolean;
  onChangeText: (content: string) => void;
  path: string;
}) {
  if (category === 'image' && getImageMimeType(path) !== 'image/svg+xml') {
    // Binary images open without content (the text content endpoint can't return
    // them); show a placeholder rather than a broken image. If a binary-safe
    // endpoint later supplies base64/data-URI content, render it.
    if (!content) {
      return (
        <View style={editorShellStyles.fileUnsupportedPreview}>
          <File color={themedColor('#A1A1AA')} size={32} strokeWidth={2.2} />
          <Text style={editorShellStyles.fileUnsupportedTitle}>Image preview</Text>
          <Text style={editorShellStyles.fileUnsupportedBody}>
            This file is available in the sandbox. Open it in the web app to view it — native preview of binary files isn&apos;t enabled in this build yet.
          </Text>
        </View>
      );
    }
    const source = content.startsWith('data:')
      ? content
      : `data:${getImageMimeType(path)};base64,${content}`;
    return (
      <ScrollView contentContainerStyle={editorShellStyles.fileImagePreviewWrap}>
        <Image resizeMode="contain" source={{ uri: source }} style={editorShellStyles.fileImagePreview} />
      </ScrollView>
    );
  }

  if (category === 'pdf') {
    return (
      <View style={editorShellStyles.fileUnsupportedPreview}>
        <File color={themedColor('#A1A1AA')} size={32} strokeWidth={2.2} />
        <Text style={editorShellStyles.fileUnsupportedTitle}>PDF preview</Text>
        <Text style={editorShellStyles.fileUnsupportedBody}>
          This file is available in the sandbox. Native PDF viewing is not enabled in this build yet.
        </Text>
      </View>
    );
  }

  return (
    <TextInput
      autoCapitalize="none"
      autoCorrect={false}
      editable={editable}
      multiline
      onChangeText={onChangeText}
      scrollEnabled
      spellCheck={false}
      style={[
        editorShellStyles.fileSourceInput,
        !editable && editorShellStyles.fileSourceInputReadOnly,
      ]}
      textAlignVertical="top"
      value={content}
    />
  );
}
