import React, { useEffect } from "react";
import { MessageItem, SystemNote } from "./NodeRenderer";
import { useFlowConversation, isWaiting, isTerminal } from "./useFlowConversation";
import { CONVERSATION_STATUS } from "@/shared/status";
import useStickToLast from "@/shared/useStickToLast";
import NewMessages from "@/shared/NewMessages";

// ONE ENGAGEMENT, rendered as chat — INSIDE the vendor's room since 17/09/2026.
//
// This was a page of its own, "Booking chat", reached by leaving the room and returned from by a
// back button. Two surfaces drew the same conversation and only one of them could draw it properly:
// here a live day picker and the answers written back into the questions that asked them, there
// "Pick a time that suits you." as a flat bubble with nothing to press. The owner: "the conversation
// in user panel is better … remove this page, all functions in this page should be migrated to
// here". So the page went and the renderer stayed, and ChatTab hands it the selected engagement.
//
// A flow only accepts the affordance its current node asked for, so every ANSWER renders inline in
// the message that requested it (see NodeRenderer). The composer below belongs to the room and
// writes a plain message into it — how a member asks a question mid-flow, which the engine neither
// sees nor advances on.
//
// `renderEnded` is what to offer once the run is over — the vendor's other services, so a
// finished booking is not a dead end. It takes the org id because only the run knows which
// vendor this room belongs to; the markup stays with the panel that owns the service list.
export default function FlowChat({ conversationId, renderEnded, roomActions = null }) {
    const {
        conversation, messages, phase, fatal, submitting,
        submitInput, submitEvidence, submitAck, uploadEvidence, submitSignature, submitForm,
    } = useFlowConversation(conversationId);

    // The same rule the room uses. This was `endRef.scrollIntoView({ block: "end" })`, which is the
    // right answer only while every message fits: the LAST one can be taller than the window — a
    // reschedule card carrying thirty slots is — and pinning its bottom drops the reader halfway
    // down a slot list with the question that introduced it scrolled away. It never showed here
    // until this surface began drawing the room's cards on 02/09/2026, and then it showed at once.
    // It also means scrolling UP now stays put, as it already did in the room. (Arfu)
    const { ref: bodyRef, onScroll, repin, behind, jumpToLatest } = useStickToLast(messages.length);

    // Opening a different conversation starts at its newest message.
    useEffect(() => { repin(); }, [conversationId]);

    if (phase === "loading") {
        return <div className="wg-chat"><SystemNote>Loading…</SystemNote></div>;
    }
    if (phase === "error") {
        return <div className="wg-chat"><SystemNote>{fatal}</SystemNote></div>;
    }

    const status = conversation?.status;
    const currentNode = conversation?.current_node;
    const waiting = isWaiting(status);

    // WHICH message carries the live affordance. Three things have to be true of it: it belongs to
    // this run, it is the node the engine is parked on, and it is the one the engine spoke.
    //
    // It used to be "any message whose node_id matches", which had two faults that only showed once
    // this surface began loading the whole room (02/09/2026). A re-sent card — the payment deadline
    // chases by sending the invoice AGAIN — shares its node id with the first, so both went live and
    // the customer saw two payment forms. And an older run in the same room can be parked on a node
    // of the same name. Taking the LAST match answers both: the affordance belongs to the most
    // recent ask, which is the one on screen at the bottom.
    const activeIndex = (() => {
        for (let i = messages.length - 1; i >= 0; i--) {
            const m = messages[i];
            if (m.direction === "outbound" && m.node_id === currentNode
                && (!m.flow_run_id || m.flow_run_id === conversationId)) return i;
        }
        return -1;
    })();

    // uploadEvidence was MISSING here from the day the evidence step learned to take files
    // (03/09/2026): the hook exported it, nothing passed it down, so the input's `onUpload` was
    // undefined, `onUpload?.(files)` answered undefined, and every attached file was dropped on the
    // floor while the answer went through with text alone. Nothing failed — the customer saw their
    // answer sent, and the org got a verification card with nothing on it. (Arfu found it in the
    // Inbox: "the upload doc/image not shown".)
    // NAMED, not spread: the hook returns state as well as actions, and a card must not be handed
    // `messages`. The cost is that a new action has to be added here too — `submitForm` was added
    // to the hook and not to this list, so Send answers called undefined and did nothing.
    // (Arfu, 10/09/2026)
    const actions = { submitInput, submitEvidence, submitAck, uploadEvidence, submitSignature, submitForm };

    return (
        <>
        <div className="wg-chat" ref={bodyRef} onScroll={onScroll}>
            {/* A run with NOTHING in it rendered as a blank page — no transcript, no card, nothing
                to say why. It should not happen (the engine writes its first message as it starts)
                and it did: a run begun while its flow was being republished on 17/09/2026 kept its
                node and lost its question. Say so rather than showing an empty screen. */}
            {messages.length === 0 ? (
                <div className="wg-empty" style={{ padding: "32px 20px" }}>
                    <div className="wg-empty-t">Nothing in this conversation</div>
                    <div className="wg-empty-s">
                        It was started but never said anything. Message the team below, or start again from Services.
                    </div>
                </div>
            ) : null}
            {messages.map((m, i) => (
                <MessageItem
                    key={m.id ?? i}
                    message={m}
                    ctx={{
                        index: i,
                        messages,
                        actions,
                        roomActions,
                        // Live only while the engine is parked on this exact message, never while
                        // a submit is already in flight — and never when the step is addressed to
                        // the OTHER side. The server says which (`for_you`); before that a customer
                        // was shown the supplier's Accept and Decline with the buttons live, and
                        // pressing one earned a refusal. (Arfu, 16/09/2026)
                        active: waiting && !submitting && i === activeIndex && m.for_you !== false,
                        // The step the run is parked on, whoever it is addressed to. A card for the
                        // other side is only "waiting" while it IS the current one — an answered one
                        // is history and should read as history.
                        isCurrent: waiting && i === activeIndex,
                        // A `match` card asks the server who could take this case, so it needs to
                        // know which case it is on.
                        conversationId,
                        // Keyed on the ASK, not on the last row. A booking-status announcement or a
                        // vendor reply arriving after the question used to push it off the end and
                        // take the "Got it" button with it — the one thing the customer had to do.
                        waitingAck: status === CONVERSATION_STATUS.WAITING_ACK && i === activeIndex,
                    }}
                />
            ))}

            {submitting ? <SystemNote>Sending…</SystemNote> : null}
            {status === CONVERSATION_STATUS.WAITING_API ? <SystemNote>Working on it…</SystemNote> : null}
            {status === CONVERSATION_STATUS.DONE ? <SystemNote>This conversation is complete.</SystemNote> : null}
            {status === CONVERSATION_STATUS.FAILED ? <SystemNote>This conversation ended unexpectedly.</SystemNote> : null}
            {isTerminal(status) ? renderEnded?.(conversation?.organization_id) : null}

        </div>
        {/* Outside the scroller on purpose: inside it, the nudge would scroll away with the
            messages it is announcing. .wg-surface is the positioned ancestor. */}
        <NewMessages count={behind} onClick={jumpToLatest} />
        </>
    );
}
