import { Form, Head, Link } from '@inertiajs/react';
import {
    BriefcaseBusiness,
    Info,
    MessageSquareWarning,
    Pencil,
    UserRound,
} from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import TicketController from '@/actions/App/Http/Controllers/TicketController';
import { Combobox, type ComboboxOption } from '@/components/crm/combobox';
import { FormActions } from '@/components/crm/form-field';
import { Callout, PageHeader, SectionCard } from '@/components/crm/page-header';
import { FormField, NativeSelect } from '@/components/crm/native-select';
import { TicketStatusOptions } from '@/components/crm/ticket-status-options';
import { RichTextEditor } from '@/components/crm/rich-text-editor';
import { StaffPicker } from '@/components/crm/staff-picker';
import { StatusPill } from '@/components/crm/status-badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { formatDateTime, toDateInputValue } from '@/lib/format';
import {
    customers as customersLookup,
    jobs as jobsLookup,
} from '@/routes/lookups';
import { index as ticketsIndex, show as ticketShow } from '@/routes/tickets';
import type {
    TicketCustomerOption,
    TicketEditProps,
    TicketJobOption,
} from '@/types';

const textareaClassName =
    'border-input dark:bg-input/30 min-h-24 w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]';

/** Statuses where the outcome has to be written down. */
const RESOLVED_STATUSES = ['resolved', 'closed'];

type Contact = { name: string; phone: string; email: string };

export default function TicketsEdit({
    ticket,
    resolutionCodes,
    options,
    customers,
    jobs,
    can,
}: TicketEditProps) {
    // The page is sent the job the ticket already points at and nothing else:
    // the rest of the customer's work is fetched when one is named. Its
    // customer_id is how a ticket imported with a job but no customer still
    // opens with both filled in.
    const pinnedJobs = useRef(jobs);

    const [customerId, setCustomerId] = useState<number | null>(
        () =>
            ticket.client?.id ??
            pinnedJobs.current.find((job) => job.value === ticket.job?.id)
                ?.customer_id ??
            null,
    );
    const [jobId, setJobId] = useState<number | null>(
        () => ticket.job?.id ?? null,
    );
    const [installerId, setInstallerId] = useState<number | null>(
        () => ticket.installer?.id ?? null,
    );
    // Everyone already holding the ticket, in the order they were put on it.
    const [assigneeIds, setAssigneeIds] = useState<number[]>(() =>
        ticket.assignees.map((member) => member.id),
    );
    const [status, setStatus] = useState<string>(ticket.status.value);
    // The category can be cleared as well as changed, which is why it is held
    // in state: "uncategorised" is a real answer.
    const [category, setCategory] = useState<string>(
        ticket.category?.value ?? '',
    );
    // Kept exactly as the ticket recorded it until somebody picks a different
    // customer, which is the point of storing contact details on the ticket.
    const [contact, setContact] = useState<Contact>(() => ({
        name: ticket.contact.name ?? '',
        phone: ticket.contact.phone ?? '',
        email: ticket.contact.email ?? '',
    }));

    // The customer list is searched on the server, so the page carries only the
    // customer this ticket is about and anyone chosen while it is open.
    const [customerOptions, setCustomerOptions] =
        useState<TicketCustomerOption[]>(customers);
    const [jobOptions, setJobOptions] = useState<TicketJobOption[]>(jobs);
    const [loadingJobs, setLoadingJobs] = useState(false);

    // Read inside the fetch callback, so a job the ticket is already linked to
    // is never replaced by the one job the customer happens to have.
    const jobIdRef = useRef<number | null>(jobId);

    useEffect(() => {
        jobIdRef.current = jobId;
    }, [jobId]);

    const selectedCustomer =
        customerOptions.find((customer) => customer.value === customerId) ??
        null;
    const selectedJob = jobOptions.find((job) => job.value === jobId) ?? null;

    useEffect(() => {
        if (customerId === null) {
            setJobOptions(pinnedJobs.current);
            setLoadingJobs(false);

            return;
        }

        const controller = new AbortController();
        setLoadingJobs(true);

        fetch(`${jobsLookup().url}?customer=${customerId}`, {
            headers: { Accept: 'application/json' },
            signal: controller.signal,
        })
            .then((response) =>
                response.ok
                    ? (response.json() as Promise<{
                          options?: TicketJobOption[];
                      }>)
                    : { options: [] },
            )
            .then((payload) => {
                if (controller.signal.aborted) {
                    return;
                }

                const listed = payload.options ?? [];

                setJobOptions(listed);

                // A ticket with no job on a customer who has exactly one is
                // almost always about that job, so it is offered rather than
                // left to be hunted down.
                if (jobIdRef.current === null && listed.length === 1) {
                    setJobId(listed[0].value);
                }
            })
            .catch(() => {
                // A cancelled request is the normal case when the customer is
                // changed twice in a row, and the picker simply stays empty.
            })
            .finally(() => {
                if (!controller.signal.aborted) {
                    setLoadingJobs(false);
                }
            });

        return () => controller.abort();
    }, [customerId]);

    /**
     * A ticket's job is always one of the customer's own jobs, so the picker is
     * shut until a customer is named. The job the ticket already points at is
     * kept even when the fetched list does not carry it, so opening the form
     * never quietly loses the link.
     */
    const customerJobs = useMemo(() => {
        const listed = [...jobOptions];

        if (jobId !== null && !listed.some((job) => job.value === jobId)) {
            const pinned = pinnedJobs.current.find(
                (job) => job.value === jobId,
            );

            if (pinned !== undefined) {
                listed.unshift(pinned);
            }
        }

        return listed.map((job) => ({
            value: job.value,
            label: job.label,
            note: job.installer
                ? `Installed by ${job.installer}`
                : job.installer_company
                  ? `Installed by ${job.installer_company} — not in the register`
                  : 'No installer on this job yet',
            meta: job.status,
        }));
    }, [jobOptions, jobId]);

    /**
     * The same rules as on the create screen: naming the customer fills in who
     * we are dealing with and narrows the jobs, and linking a job pulls in who
     * installed it, so the installer answering the ticket is never a guess.
     */
    const handleCustomerChange = (
        nextId: number | null,
        option: ComboboxOption | null,
    ) => {
        const chosen = option as TicketCustomerOption | null;

        if (chosen !== null) {
            setCustomerOptions((previous) =>
                previous.some((customer) => customer.value === chosen.value)
                    ? previous
                    : [chosen, ...previous],
            );
        }

        setCustomerId(nextId);
        setContact({
            name: chosen?.contact.name ?? '',
            phone: chosen?.contact.phone ?? '',
            email: chosen?.contact.email ?? '',
        });

        if (selectedJob && selectedJob.customer_id !== nextId) {
            setJobId(null);
        }
    };

    const handleJobChange = (
        nextId: number | null,
        option: ComboboxOption | null,
    ) => {
        const job = option as TicketJobOption | null;

        setJobId(nextId);
        setInstallerId(job?.installer_id ?? installerId);
    };

    return (
        <>
            <Head title={`Edit ${ticket.display_number}`} />

            <div className="flex flex-col gap-5">
                <PageHeader
                    eyebrow={
                        <>
                            <Pencil className="size-3.5 text-solar" />
                            <span className="font-mono crm-num">
                                {ticket.display_number}
                            </span>
                            <span
                                aria-hidden
                                className="text-muted-foreground/50"
                            >
                                ·
                            </span>
                            <span>{ticket.client?.name ?? 'No customer'}</span>
                        </>
                    }
                    title={`Edit ${ticket.display_number}`}
                    description={ticket.issue}
                    actions={
                        <>
                            <Button variant="outline" size="sm" asChild>
                                <Link href={ticketShow(ticket.id)}>
                                    Back to ticket
                                </Link>
                            </Button>
                            <Button variant="ghost" size="sm" asChild>
                                <Link href={ticketsIndex()}>All tickets</Link>
                            </Button>
                        </>
                    }
                />

                <Callout
                    tone="info"
                    icon={Info}
                    title="This form keeps the record accurate"
                >
                    <p className="flex flex-wrap items-center gap-x-2 gap-y-1">
                        <span>
                            Import id{' '}
                            <span className="font-mono font-medium crm-num">
                                {ticket.ticket_import_id ?? 'not imported'}
                            </span>{' '}
                            — the id this ticket has in the SMS system, which is
                            what makes repeat deliveries safe.
                        </span>
                        <StatusPill tone={ticket.status.color}>
                            {ticket.status.label}
                        </StatusPill>
                    </p>
                    <p className="mt-1">
                        Changing the status here also stamps the
                        acknowledgement, resolution and close times on the
                        ticket.
                    </p>
                </Callout>

                <Form
                    {...TicketController.update.form({ ticket: ticket.id })}
                    className="flex flex-col gap-5"
                >
                    {({ processing, errors }) => (
                        <>
                            <SectionCard
                                title="What was reported"
                                icon={MessageSquareWarning}
                                tone="warning"
                            >
                                <div className="grid gap-x-6 gap-y-4 md:grid-cols-2">
                                    <FormField
                                        label="Issue"
                                        htmlFor="issue"
                                        error={errors.issue}
                                        required
                                        className="md:col-span-2"
                                    >
                                        <Input
                                            id="issue"
                                            name="issue"
                                            defaultValue={ticket.issue}
                                            required
                                        />
                                    </FormField>

                                    <FormField
                                        label="Detail"
                                        htmlFor="body"
                                        error={errors.body}
                                        hint="Formatting is kept; the sanitizer drops anything unsafe"
                                        className="md:col-span-2"
                                    >
                                        <RichTextEditor
                                            name="body"
                                            id="body"
                                            // The server sends the stored
                                            // description ready to render, so
                                            // the editor opens on exactly what
                                            // is saved.
                                            value={ticket.body_html}
                                        />
                                    </FormField>

                                    <FormField
                                        label="Priority"
                                        htmlFor="priority"
                                        error={errors.priority}
                                        required
                                    >
                                        <NativeSelect
                                            id="priority"
                                            name="priority"
                                            defaultValue={ticket.priority.value}
                                        >
                                            {options.priorities.map(
                                                (priority) => (
                                                    <option
                                                        key={priority.value}
                                                        value={priority.value}
                                                    >
                                                        {priority.label}
                                                    </option>
                                                ),
                                            )}
                                        </NativeSelect>
                                    </FormField>

                                    <FormField
                                        label="Status"
                                        htmlFor="status"
                                        error={errors.status}
                                        hint={ticket.status.waiting_on}
                                        required
                                    >
                                        <NativeSelect
                                            id="status"
                                            name="status"
                                            value={status}
                                            onChange={(event) =>
                                                setStatus(event.target.value)
                                            }
                                        >
                                            <TicketStatusOptions
                                                statuses={
                                                    options.ticketStatuses
                                                }
                                            />
                                        </NativeSelect>
                                    </FormField>

                                    <FormField
                                        label="Category"
                                        htmlFor="category"
                                        error={errors.category}
                                        hint={
                                            options.ticketCategories.find(
                                                (option) =>
                                                    option.value === category,
                                            )?.description ??
                                            'Not decided yet — pick what kind of work this is'
                                        }
                                    >
                                        <NativeSelect
                                            id="category"
                                            name="category"
                                            value={category}
                                            onChange={(event) =>
                                                setCategory(event.target.value)
                                            }
                                        >
                                            <option value="">
                                                Not categorised yet
                                            </option>
                                            {options.ticketCategories.map(
                                                (option) => (
                                                    <option
                                                        key={option.value}
                                                        value={option.value}
                                                    >
                                                        {option.label}
                                                    </option>
                                                ),
                                            )}
                                        </NativeSelect>
                                    </FormField>

                                    <FormField
                                        label="Source"
                                        htmlFor="source"
                                        error={errors.source}
                                        required
                                    >
                                        <NativeSelect
                                            id="source"
                                            name="source"
                                            defaultValue={ticket.source.value}
                                        >
                                            {options.ticketSources.map(
                                                (source) => (
                                                    <option
                                                        key={source.value}
                                                        value={source.value}
                                                    >
                                                        {source.label}
                                                    </option>
                                                ),
                                            )}
                                        </NativeSelect>
                                    </FormField>

                                    <FormField
                                        label="Reported on"
                                        htmlFor="reported_at"
                                        error={errors.reported_at}
                                    >
                                        <Input
                                            id="reported_at"
                                            name="reported_at"
                                            type="date"
                                            defaultValue={toDateInputValue(
                                                ticket.reported_at,
                                            )}
                                        />
                                    </FormField>
                                </div>
                            </SectionCard>

                            <SectionCard
                                title="Who it is about"
                                description="Customer, job and the installer who did the work, plus whoever in the office is holding the ticket."
                                icon={BriefcaseBusiness}
                                tone="solar"
                            >
                                <div className="grid gap-x-6 gap-y-4 md:grid-cols-2 xl:grid-cols-4">
                                    <FormField
                                        label="Customer"
                                        htmlFor="client_id"
                                        error={errors.client_id}
                                        hint="Start typing a name, number or phone"
                                    >
                                        <Combobox
                                            id="client_id"
                                            name="client_id"
                                            value={customerId}
                                            onChange={handleCustomerChange}
                                            options={customerOptions.map(
                                                (customer) => ({
                                                    value: customer.value,
                                                    label: customer.label,
                                                    note: customer.note,
                                                }),
                                            )}
                                            remote={{
                                                url: customersLookup().url,
                                            }}
                                            placeholder="Search customers"
                                            clearLabel="Not matched yet"
                                            emptyLabel="No customer matches that"
                                        />
                                    </FormField>

                                    <FormField
                                        label="Job"
                                        htmlFor="job_id"
                                        error={errors.job_id}
                                        hint={
                                            selectedJob?.status
                                                ? `Job is ${selectedJob.status}`
                                                : selectedCustomer
                                                  ? 'Their own work is listed, and filled in when they only have one job'
                                                  : 'Choose the customer first — a job is always theirs'
                                        }
                                    >
                                        <Combobox
                                            id="job_id"
                                            name="job_id"
                                            value={jobId}
                                            onChange={handleJobChange}
                                            options={customerJobs}
                                            disabled={customerId === null}
                                            placeholder={
                                                customerId === null
                                                    ? 'Choose the customer first'
                                                    : 'Search their jobs'
                                            }
                                            clearLabel="No job linked"
                                            emptyLabel={
                                                loadingJobs
                                                    ? 'Loading their jobs…'
                                                    : 'No jobs on this customer'
                                            }
                                        />
                                    </FormField>

                                    <FormField
                                        label="Installer"
                                        htmlFor="installer_id"
                                        error={errors.installer_id}
                                        hint={
                                            selectedJob?.installer
                                                ? `Filled in from the job — installed by ${selectedJob.installer}`
                                                : selectedJob?.installer_company
                                                  ? `The job names ${selectedJob.installer_company}, who is not in the register`
                                                  : 'Whoever did the install answers for it'
                                        }
                                    >
                                        <Combobox
                                            id="installer_id"
                                            name="installer_id"
                                            value={installerId}
                                            onChange={setInstallerId}
                                            options={options.installers.map(
                                                (installer) => ({
                                                    value: installer.value,
                                                    label: installer.label,
                                                    note: installer.note,
                                                }),
                                            )}
                                            placeholder="Search installers"
                                            clearLabel="Nobody assigned"
                                        />
                                    </FormField>

                                    {/* Reassigning is its own permission, which
                                        the server enforces too. Whoever is
                                        added or taken off here is the whole
                                        set on the ticket afterwards. */}
                                    {can.assign ? (
                                        <FormField
                                            label="Assigned to"
                                            htmlFor="assignees"
                                            error={
                                                errors.assignees ??
                                                errors['assignees.0']
                                            }
                                            hint="Everyone in the office holding this ticket — the installer answers for the work, these people answer to the customer"
                                        >
                                            <StaffPicker
                                                id="assignees"
                                                name="assignees"
                                                value={assigneeIds}
                                                onChange={setAssigneeIds}
                                                options={options.staff}
                                            />
                                        </FormField>
                                    ) : null}
                                </div>

                                <p className="mt-4 text-xs text-muted-foreground">
                                    Reported{' '}
                                    {formatDateTime(ticket.reported_at)}
                                    {ticket.creator
                                        ? ` · logged by ${ticket.creator.name}`
                                        : ''}
                                </p>
                            </SectionCard>

                            <SectionCard
                                title="Contact and outcome"
                                icon={UserRound}
                                tone="slate"
                            >
                                <div className="grid gap-x-6 gap-y-4 md:grid-cols-3">
                                    <FormField
                                        label="Name"
                                        htmlFor="contact_name"
                                        error={errors.contact_name}
                                    >
                                        <Input
                                            id="contact_name"
                                            name="contact_name"
                                            value={contact.name}
                                            onChange={(event) =>
                                                setContact({
                                                    ...contact,
                                                    name: event.target.value,
                                                })
                                            }
                                        />
                                    </FormField>

                                    <FormField
                                        label="Phone"
                                        htmlFor="contact_phone"
                                        error={errors.contact_phone}
                                    >
                                        <Input
                                            id="contact_phone"
                                            name="contact_phone"
                                            value={contact.phone}
                                            onChange={(event) =>
                                                setContact({
                                                    ...contact,
                                                    phone: event.target.value,
                                                })
                                            }
                                        />
                                    </FormField>

                                    <FormField
                                        label="Email"
                                        htmlFor="contact_email"
                                        error={errors.contact_email}
                                    >
                                        <Input
                                            id="contact_email"
                                            name="contact_email"
                                            type="email"
                                            value={contact.email}
                                            onChange={(event) =>
                                                setContact({
                                                    ...contact,
                                                    email: event.target.value,
                                                })
                                            }
                                        />
                                    </FormField>
                                </div>

                                {RESOLVED_STATUSES.includes(status) ? (
                                    <div className="mt-5 flex flex-col gap-4 rounded-lg border border-success/30 bg-success-soft/30 p-3">
                                        <p className="text-xs font-medium text-success">
                                            The outcome — what is kept after the
                                            conversation is over
                                        </p>

                                        <FormField
                                            label="What fixed it"
                                            htmlFor="resolution"
                                            error={errors.resolution}
                                            hint="Required before a ticket can be finished"
                                        >
                                            <textarea
                                                id="resolution"
                                                name="resolution"
                                                className={textareaClassName}
                                                defaultValue={
                                                    ticket.resolution ?? ''
                                                }
                                                placeholder="What was wrong, and what was done about it."
                                            />
                                        </FormField>

                                        <FormField
                                            label="Kind of outcome"
                                            htmlFor="resolution_code"
                                            error={errors.resolution_code}
                                            hint="How it closed, so the figures afterwards can tell a repair from a warranty claim."
                                        >
                                            <NativeSelect
                                                id="resolution_code"
                                                name="resolution_code"
                                                defaultValue={
                                                    ticket.resolution_code
                                                        ?.value ?? ''
                                                }
                                            >
                                                <option value="">
                                                    Choose an outcome…
                                                </option>
                                                {resolutionCodes.map(
                                                    (option) => (
                                                        <option
                                                            key={option.value}
                                                            value={option.value}
                                                        >
                                                            {option.label}
                                                        </option>
                                                    ),
                                                )}
                                            </NativeSelect>
                                        </FormField>
                                    </div>
                                ) : null}
                            </SectionCard>

                            <SectionCard
                                title="Save"
                                icon={Pencil}
                                tone="slate"
                            >
                                <FormActions
                                    note={`Last updated ${formatDateTime(ticket.dates.updated_at)}. Status changes are time-stamped automatically.`}
                                >
                                    <Button variant="ghost" asChild>
                                        <Link href={ticketShow(ticket.id)}>
                                            Cancel changes
                                        </Link>
                                    </Button>
                                    <Button type="submit" disabled={processing}>
                                        Save changes
                                    </Button>
                                </FormActions>
                            </SectionCard>
                        </>
                    )}
                </Form>
            </div>
        </>
    );
}

TicketsEdit.layout = {
    breadcrumbs: [
        {
            title: 'Tickets',
            href: ticketsIndex(),
        },
    ],
};
