import React from 'react';
import { Box, Text } from 'ink';
import TextInput from 'ink-text-input';
import terminalService from '../terminalService.js';

class Pane extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            cursorVisible: true,
        };
        this._cursorTimer = null;
    }
    componentDidMount() {
        if (this.props.showCursor) {
            this._cursorTimer = setInterval(() => {
                this.setState((s) => ({ cursorVisible: !s.cursorVisible }));
            }, typeof this.props.cursorBlinkMs === 'number' && this.props.cursorBlinkMs > 0 ? this.props.cursorBlinkMs : 500);
        }
    }
    componentWillUnmount() {
        if (this._cursorTimer) {
            clearInterval(this._cursorTimer);
            this._cursorTimer = null;
        }
    }
    // Strip ANSI escape sequences so width measurement/truncation is accurate
    stripAnsi(input) {
        if (input == null) return '';
        const str = String(input);
        const ansiPattern = /\u001B\[[0-9;?]*[ -\/]*[@-~]/g; // ESC[ ... cmd
        return str.replace(ansiPattern, '');
    }
    // Expand tab stops to spaces using 8-column tabs (like a typical terminal)
    expandTabs(input, tabWidth, limit) {
        const width = typeof tabWidth === 'number' && tabWidth > 0 ? tabWidth : 8;
        const max = typeof limit === 'number' && limit > 0 ? limit : Infinity;
        let col = 0;
        let out = '';
        for (let i = 0; i < input.length; i += 1) {
            const ch = input[i];
            if (ch === '\t') {
                const spaces = width - (col % width);
                // If adding spaces exceeds max, clamp
                const add = Math.min(spaces, Math.max(0, max - col));
                out += ' '.repeat(add);
                col += add;
                if (col >= max) break;
            } else {
                // Treat as width 1 for simplicity (does not account for wide unicode)
                if (col + 1 > max) break;
                out += ch;
                col += 1;
            }
        }
        return out;
    }
    // Apply a blinking cursor to the given line according to width constraints
    withCursor(line, maxWidth) {
        const cursorChar = typeof this.props.cursorChar === 'string' && this.props.cursorChar.length > 0 ? this.props.cursorChar[0] : '█';
        const visible = !!this.state.cursorVisible;
        if (!visible) {
            return line;
        }
        if (typeof maxWidth === 'number' && maxWidth > 0) {
            if (line.length < maxWidth) {
                return `${line}${cursorChar}`;
            }
            if (line.length === maxWidth) {
                // Replace last char to avoid overflow
                return `${line.slice(0, maxWidth - 1)}${cursorChar}`;
            }
            // If somehow longer, just ensure width
            return `${line.slice(0, maxWidth - 1)}${cursorChar}`;
        }
        return `${line}${cursorChar}`;
    }
    render() {
        const { title, lines, maxWidth } = this.props;
        return (
            <Box flexDirection="column" borderStyle="round" paddingX={1} paddingY={0} flexGrow={1} flexShrink={1} minWidth={0}>
                <Text color="cyan">{title}</Text>
                <Box flexDirection="column" width="100%" flexShrink={1} minWidth={0}>
                    {(lines && lines.length > 0)
                        ? lines.map((line, index) => {
                            const isLast = index === lines.length - 1;
                            const width = typeof maxWidth === 'number' && maxWidth > 0 ? maxWidth : undefined;
                            const clean = this.stripAnsi(line);
                            const expanded = this.expandTabs(clean, 8, width);
                            const baseLine = (width && expanded.length > width) ? expanded.slice(0, width) : expanded;
                            const finalLine = (this.props.showCursor && isLast) ? this.withCursor(baseLine, width) : baseLine;
                            return (
                                <Text key={index}>{finalLine}</Text>
                            );
                        })
                        : <Text dimColor>—</Text>
                    }
                </Box>
            </Box>
        );
    }
}

export default class InkApp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            input: '',
            logs: [],
            terminal: [],
            chainOfThought: [],
            llmOutput: []
        };
        this.handleSubmit = this.handleSubmit.bind(this);
        this.handleChange = this.handleChange.bind(this);
        this.setLLMOutput = this.setLLMOutput.bind(this);
        this.setChainOfThought = this.setChainOfThought.bind(this);
        this.setTerminal = this.setTerminal.bind(this);
        this.setLogs = this.setLogs.bind(this);
    }

    componentDidMount() {
        this.terminalUnsub = (lines) => {
            this.setTerminal(lines);
        };
        terminalService.on('update', this.terminalUnsub);
        // initialize with current buffered output if any
        const initial = terminalService.getLines();
        if (initial && initial.length) {
            this.setTerminal(initial);
        }

        // Resize PTY columns to match the Terminal pane width on start and on TTY resize
        this.onResize = () => {
            const totalCols = (process && process.stdout && process.stdout.columns) ? process.stdout.columns : 80;
            const columnWidth = Math.max(1, Math.floor(totalCols / 2));
            const paneContentWidth = Math.max(1, columnWidth - 4); // borders + padding
            terminalService.resize(paneContentWidth);
        };
        if (process.stdout && process.stdout.on) {
            process.stdout.on('resize', this.onResize);
        }
        this.onResize();
    }
    componentWillUnmount() {
        if (this.terminalUnsub) {
            terminalService.off('update', this.terminalUnsub);
            this.terminalUnsub = null;
        }
        if (this.onResize && process.stdout && process.stdout.off) {
            process.stdout.off('resize', this.onResize);
            this.onResize = null;
        }
    }

    setPaneLines(stateKey, lines) {
        if (typeof stateKey !== 'string' || !(stateKey in this.state)) {
            throw new Error(`Invalid state key: ${String(stateKey)}`);
        }
        if (lines === undefined) {
            this.setState({ [stateKey]: [] });
            return;
        }
        if (!Array.isArray(lines)) {
            throw new TypeError(`Expected an array of lines or undefined for ${stateKey}`);
        }
        this.setState({ [stateKey]: lines });
    }

    setLLMOutput(lines) {
        this.setPaneLines('llmOutput', lines);
    }

    setChainOfThought(lines) {
        this.setPaneLines('chainOfThought', lines);
    }

    setTerminal(lines) {
        this.setPaneLines('terminal', lines);
    }

    setLogs(lines) {
        this.setPaneLines('logs', lines);
    }

    handleChange(value) {
        this.setState({ input: value });
    }

    handleSubmit() {
        const { input } = this.state;
        if (!input) return;
        try {
            terminalService.write(`${input}\r`);
        } catch (e) {
            // do not hide errors; show in logs
            this.setState((state) => ({
                logs: [...state.logs, `! write error: ${String(e && e.message ? e.message : e)}`],
            }));
        }
        this.setState((state) => ({
            logs: [...state.logs, `> ${input}`],
            input: ''
        }));
    }

    render() {
        const { input, logs, terminal, chainOfThought, llmOutput } = this.state;
        const totalCols = (process && process.stdout && process.stdout.columns) ? process.stdout.columns : 80;
        const totalRows = (process && process.stdout && process.stdout.rows) ? process.stdout.rows : 24;
        const columnWidth = Math.max(1, Math.floor(totalCols / 2));
        const paneContentWidth = Math.max(1, columnWidth - 4); // borders (2) + paddingX=1 on both sides (2)

        // Compute how many lines fit vertically per pane
        // Reserve ~2 rows for the input area (label + margin) at bottom
        const reservedFooterRows = 2;
        const panesAreaRows = Math.max(4, totalRows - reservedFooterRows);
        const paneOuterHeight = Math.max(4, Math.floor(panesAreaRows / 2));
        // Remove top/bottom border (2) and title line (1)
        const paneContentHeight = Math.max(0, paneOuterHeight - 3);

        const sliceLast = (arr) => {
            if (!Array.isArray(arr)) return [];
            if (paneContentHeight <= 0) return [];
            if (arr.length <= paneContentHeight) return arr;
            return arr.slice(arr.length - paneContentHeight);
        };

        const llmOutputView = sliceLast(llmOutput);
        const chainOfThoughtView = sliceLast(chainOfThought);
        const terminalView = sliceLast(terminal);
        const logsView = sliceLast(logs);
        return (
            <Box flexDirection="column" height="100%">
                <Box flexGrow={1} flexDirection="row" minWidth={0}>
                    <Box flexGrow={1} flexDirection="column" minWidth={0}>
                        <Pane title="LLM Output" lines={llmOutputView} maxWidth={paneContentWidth} />
                        <Pane title="Chain of Thought" lines={chainOfThoughtView} maxWidth={paneContentWidth} />
                    </Box>
                    <Box flexGrow={1} flexDirection="column" minWidth={0}>
                        <Pane title="Terminal" lines={terminalView} maxWidth={paneContentWidth} showCursor cursorBlinkMs={600} />
                        <Pane title="Logging" lines={logsView} maxWidth={paneContentWidth} />
                    </Box>
                </Box>
                <Box marginTop={1}>
                    <Text>Input: </Text>
                    <TextInput
                        value={input}
                        onChange={this.handleChange}
                        onSubmit={this.handleSubmit}
                        placeholder="Type and press Enter..."
                    />
                </Box>
            </Box>
        );
    }
}


