import { Form, Head, Link } from '@inertiajs/react';
import {
    BriefcaseBusiness,
    Camera,
    Info,
    MessageSquareWarning,
    Plus,
    UserRound,
} from 'lucide-react';
import { useEffect, useMemo, useRef, useState } from 'react';
import TicketController from '@/actions/App/Http/Controllers/TicketController';
import { AttachmentPicker } from '@/components/crm/attachment-picker';
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 { RichTextEditor } from '@/components/crm/rich-text-editor';
import { StaffPicker } from '@/components/crm/staff-picker';
import { TicketStatusOptions } from '@/components/crm/ticket-status-options';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { toDateInputValue } from '@/lib/format';
import {
    customers as customersLookup,
    jobs as jobsLookup,
} from '@/routes/lookups';
import {
    create as ticketsCreate,
    index as ticketsIndex,
} from '@/routes/tickets';
import type {
    TicketCreateProps,
    TicketCustomerOption,
    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 };

const EMPTY_CONTACT: Contact = { name: '', phone: '', email: '' };

export default function TicketsCreate({
    options,
    customers,
    jobs,
    selected,
    can,
    current_staff_id,
}: TicketCreateProps) {
    // The screen can be opened from a customer or a job (?customer=, ?job=), in
    // which case the job tells us whose job it is even when nobody said so.
    const preselectedJob =
        jobs.find((candidate) => candidate.value === selected.job_id) ?? null;

    const [customerId, setCustomerId] = useState<number | null>(
        () => selected.customer_id ?? preselectedJob?.customer_id ?? null,
    );
    const [jobId, setJobId] = useState<number | null>(
        () => preselectedJob?.value ?? null,
    );
    const [installerId, setInstallerId] = useState<number | null>(
        () => preselectedJob?.installer_id ?? null,
    );
    // However many people in the office take this ticket on. It starts on the
    // person logging it — they took the call, so the ticket is theirs until
    // they hand it on — and the server does the same when the field is empty.
    const [assigneeIds, setAssigneeIds] = useState<number[]>(() =>
        can.assign && current_staff_id ? [current_staff_id] : [],
    );
    const [status, setStatus] = useState<string>('open');
    // What kind of work it is. Left blank by default: an uncategorised ticket
    // says so, which is more honest than being filed under a guess.
    const [category, setCategory] = useState<string>('');
    const [contact, setContact] = useState<Contact>(() => {
        const customer =
            customers.find(
                (candidate) =>
                    candidate.value ===
                    (selected.customer_id ?? preselectedJob?.customer_id),
            ) ?? null;

        return customer
            ? {
                  name: customer.contact.name ?? '',
                  phone: customer.contact.phone ?? '',
                  email: customer.contact.email ?? '',
              }
            : EMPTY_CONTACT;
    });

    // The customer list is searched on the server — there are thousands of
    // them — so the page carries whoever it was pointed at, plus anyone chosen
    // while it is open.
    const [customerOptions, setCustomerOptions] =
        useState<TicketCustomerOption[]>(customers);

    // The customer's own jobs are fetched the moment somebody is named, which
    // is what puts their work in front of the person logging the ticket rather
    // than whatever the newest few hundred jobs happen to be.
    const pinnedJobs = useRef(jobs);
    const [jobOptions, setJobOptions] = useState<TicketJobOption[]>(jobs);
    const [loadingJobs, setLoadingJobs] = useState(false);

    // Read inside the fetch callback, so a job that is already linked 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);

                // One job on the account is almost always the job the ticket is
                // about, so it is offered rather than left to be hunted down.
                if (jobIdRef.current === null && listed.length === 1) {
                    setJobId(listed[0].value);
                    setInstallerId(listed[0].installer_id ?? null);
                }
            })
            .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]);

    /**
     * The jobs on offer: the named customer's own work. A 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]);

    /**
     * Naming the customer fills in who we are talking to, from their primary
     * contact where there is one. Clearing the customer empties it again, so the
     * contact block always describes the person the ticket is about.
     */
    const handleCustomerChange = (
        nextId: number | null,
        option: ComboboxOption | null,
    ) => {
        const chosen = option as TicketCustomerOption | null;

        // A customer found by searching is kept in the list, so the field shows
        // the name that was chosen rather than going blank behind it.
        if (chosen !== null) {
            setCustomerOptions((previous) =>
                previous.some((customer) => customer.value === chosen.value)
                    ? previous
                    : [chosen, ...previous],
            );
        }

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

        // A job that belongs to someone else is no longer on offer.
        if (selectedJob && selectedJob.customer_id !== nextId) {
            setJobId(null);
            setInstallerId(null);
        }
    };

    /**
     * Choosing a job tells us who did the work, which is exactly who has to
     * answer for it. The installer is filled in rather than locked, because the
     * person logging the ticket may know better.
     */
    const handleJobChange = (
        nextId: number | null,
        option: ComboboxOption | null,
    ) => {
        const job = option as TicketJobOption | null;

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

    const today = toDateInputValue(new Date().toISOString());

    return (
        <>
            <Head title="Log a ticket" />

            <div className="flex flex-col gap-5">
                <PageHeader
                    eyebrow={
                        <>
                            <Plus className="size-3.5 text-solar" />
                            <span>New support ticket</span>
                        </>
                    }
                    title="Log a ticket"
                    description="Use this for a ticket that arrived by phone, at the counter or in person. Anything the customer texts in is imported automatically."
                    actions={
                        <Button variant="outline" size="sm" asChild>
                            <Link href={ticketsIndex()}>Cancel</Link>
                        </Button>
                    }
                />

                <Callout
                    tone="info"
                    icon={Info}
                    title="Tickets normally arrive on their own"
                >
                    The SMS system posts new messages to the CRM, which raises
                    the ticket, matches the customer and links the installer who
                    did the job. Logging one here is for everything else, and
                    gives it the next number for today — month and day, then a
                    daily sequence, for example{' '}
                    <span className="font-mono font-medium crm-num">
                        #1019003
                    </span>
                    .
                </Callout>

                <Form
                    {...TicketController.store.form()}
                    className="flex flex-col gap-5"
                >
                    {({ processing, errors }) => (
                        <>
                            <SectionCard
                                title="What was reported"
                                description="The customer's own words are the most useful part of a ticket."
                                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}
                                        hint="One line, the way you would say it out loud"
                                        required
                                        className="md:col-span-2"
                                    >
                                        <Input
                                            id="issue"
                                            name="issue"
                                            placeholder="e.g. Inverter showing a fault code"
                                            required
                                        />
                                    </FormField>

                                    <FormField
                                        label="Detail"
                                        htmlFor="body"
                                        error={errors.body}
                                        hint="What the customer said, and anything you already know — formatting is kept"
                                        className="md:col-span-2"
                                    >
                                        <RichTextEditor
                                            name="body"
                                            id="body"
                                            placeholder="Paste or transcribe the message, and add any context."
                                        />
                                    </FormField>

                                    <FormField
                                        label="Priority"
                                        htmlFor="priority"
                                        error={errors.priority}
                                        hint="Urgent is chased within a day"
                                        required
                                    >
                                        <NativeSelect
                                            id="priority"
                                            name="priority"
                                            defaultValue="normal"
                                        >
                                            {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="Open means nobody has picked it up yet"
                                        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 ??
                                            'What kind of work it is — a fault, a warranty claim, paperwork, or a question'
                                        }
                                    >
                                        <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}
                                        hint="How it reached us"
                                        required
                                    >
                                        <NativeSelect
                                            id="source"
                                            name="source"
                                            defaultValue="manual"
                                        >
                                            {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}
                                        hint="When the customer first raised it"
                                    >
                                        <Input
                                            id="reported_at"
                                            name="reported_at"
                                            type="date"
                                            defaultValue={today}
                                        />
                                    </FormField>
                                </div>
                            </SectionCard>

                            <SectionCard
                                title="Who it is about"
                                description="Name the customer to unlock their jobs, link the job and its installer so the right person is asked to answer it, and hand the ticket to whoever in the office is holding it."
                                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="Name, phone, customer number or address — an unknown caller can stay unlinked"
                                    >
                                        <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={
                                            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>

                                    {/* Handing work to somebody is its own
                                        permission, so the picker is only
                                        offered to those who hold it. More than
                                        one person can hold the same ticket. */}
                                    {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>
                            </SectionCard>

                            <SectionCard
                                title="Contact"
                                description="Kept on the ticket as it arrived, so it stays readable if the customer record changes later."
                                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}
                                        hint="Filled in from the customer, then editable"
                                    >
                                        <Input
                                            id="contact_name"
                                            name="contact_name"
                                            placeholder="Who we are dealing with"
                                            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"
                                            placeholder="04xx xxx xxx"
                                            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"
                                            placeholder="name@example.com"
                                            value={contact.email}
                                            onChange={(event) =>
                                                setContact({
                                                    ...contact,
                                                    email: event.target.value,
                                                })
                                            }
                                        />
                                    </FormField>
                                </div>

                                {RESOLVED_STATUSES.includes(status) ? (
                                    <div className="mt-5">
                                        <FormField
                                            label="What fixed it"
                                            htmlFor="resolution"
                                            error={errors.resolution}
                                            hint="Recorded against the ticket and time-stamped when you save"
                                        >
                                            <textarea
                                                id="resolution"
                                                name="resolution"
                                                className={textareaClassName}
                                                placeholder="What was wrong, and what was done about it."
                                            />
                                        </FormField>
                                    </div>
                                ) : null}
                            </SectionCard>

                            <SectionCard
                                title="Photos and screenshots"
                                description="What the customer showed you, or what you can see from here. A screenshot of a fault code is often the whole diagnosis."
                                icon={Camera}
                                tone="solar"
                            >
                                <FormField
                                    label="Attachments"
                                    htmlFor="attachments"
                                    error={
                                        errors.attachments ??
                                        errors['attachments.0']
                                    }
                                    hint="Photos, screenshots or a PDF — up to six files, 10 MB each"
                                >
                                    <AttachmentPicker
                                        id="attachments"
                                        name="attachments[]"
                                    />
                                </FormField>
                            </SectionCard>

                            <SectionCard
                                title="Save"
                                icon={MessageSquareWarning}
                                tone="slate"
                            >
                                <FormActions note="Tickets are worked from the ticket page, where the status, installer and resolution are changed.">
                                    <Button variant="ghost" asChild>
                                        <Link href={ticketsIndex()}>
                                            Cancel
                                        </Link>
                                    </Button>
                                    <Button type="submit" disabled={processing}>
                                        Log ticket
                                    </Button>
                                </FormActions>
                            </SectionCard>
                        </>
                    )}
                </Form>
            </div>
        </>
    );
}

TicketsCreate.layout = {
    breadcrumbs: [
        {
            title: 'Tickets',
            href: ticketsIndex(),
        },
        {
            title: 'Log a ticket',
            href: ticketsCreate(),
        },
    ],
};
