import { Form, Link, router } from '@inertiajs/react';
import {
    ArrowUpRight,
    CheckCircle2,
    Download,
    History,
    Inbox,
    Lock,
    MessageSquare,
    Send,
    Trash2,
    Users,
} from 'lucide-react';
import { useState } from 'react';
import TicketCommentController from '@/actions/App/Http/Controllers/TicketCommentController';
import TicketDiscussionController from '@/actions/App/Http/Controllers/TicketDiscussionController';
import { AttachmentPicker } from '@/components/crm/attachment-picker';
import { AttachmentViewer } from '@/components/crm/attachment-viewer';
import { FormActions } from '@/components/crm/form-field';
import { FormField } from '@/components/crm/native-select';
import { EmptyState, SectionCard } from '@/components/crm/page-header';
import { RichText } from '@/components/crm/rich-text';
import { toneClasses } from '@/components/crm/tone';
import { Button } from '@/components/ui/button';
import { formatDateTime, formatRelative } from '@/lib/format';
import { cn } from '@/lib/utils';
import type {
    DiscussionPerson,
    TicketActivityEntry,
    TicketShowProps,
} from '@/types';

const textareaClassName =
    'border-input dark:bg-input/30 flex min-h-[84px] w-full rounded-md border bg-transparent px-3 py-2 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50';

/** Which slice of the feed is on screen. */
type FeedFilter = 'all' | 'customer' | 'internal' | 'changes';

/**
 * Two letters for an avatar chip, taken from whatever the person is called.
 */
function initials(name: string): string {
    const parts = name.trim().split(/\s+/).filter(Boolean);

    if (parts.length === 0) {
        return '??';
    }

    return parts
        .slice(0, 2)
        .map((part) => part[0]?.toUpperCase() ?? '')
        .join('');
}

/**
 * The people who can read the private half, as overlapping initials.
 *
 * It answers "who else is reading this?" at a glance, which is what somebody
 * about to write a candid note actually needs to know.
 */
function Participants({ people }: { people: DiscussionPerson[] }) {
    if (people.length === 0) {
        return null;
    }

    return (
        <div className="flex items-center gap-2">
            <div className="flex -space-x-2">
                {people.slice(0, 5).map((person) => (
                    <span
                        key={person.id}
                        title={
                            person.job_title
                                ? `${person.name} — ${person.job_title}`
                                : person.name
                        }
                        className="flex size-6 items-center justify-center rounded-full bg-solar-soft text-[9px] font-semibold text-solar-ink ring-2 ring-background"
                    >
                        {initials(person.name)}
                    </span>
                ))}
                {people.length > 5 ? (
                    <span className="flex size-6 items-center justify-center rounded-full bg-muted text-[9px] font-semibold text-muted-foreground ring-2 ring-background">
                        +{people.length - 5}
                    </span>
                ) : null}
            </div>
            <span className="text-xs text-muted-foreground">
                {people.length === 1
                    ? people[0].name
                    : `${people.length} on this thread`}
            </span>
        </div>
    );
}

/**
 * The whole conversation on a ticket, in one reading order.
 *
 * A support desk writes in two directions: notes for each other and replies for
 * the customer. Splitting those into two panels makes the same conversation
 * look like two, and leaves the person deciding which box a sentence belongs
 * in. So there is one feed here, and each entry wears what it is — a customer
 * reply, an internal note, or a change the application wrote down — rather than
 * living somewhere else.
 *
 * The distinction is not cosmetic. An internal note is a staff inbox message
 * with the ticket attached, so it counts as unread in the inbox and can be
 * followed up there; a customer reply is part of what the customer was told.
 * The composer at the bottom writes to whichever of the two you pick, in
 * coloured ink, so the choice is made before the words are written.
 */
export function TicketActivity({
    ticket,
    activity,
    discussion,
    canComment,
    canDiscuss,
}: {
    ticket: TicketShowProps['ticket'];
    activity: TicketShowProps['activity'];
    discussion: TicketShowProps['discussion'];
    canComment: boolean;
    canDiscuss: boolean;
}) {
    const [filter, setFilter] = useState<FeedFilter>('all');
    const [visibility, setVisibility] = useState<'internal' | 'customer'>(
        canDiscuss ? 'internal' : 'customer',
    );

    const entries =
        filter === 'all'
            ? activity.entries
            : activity.entries.filter((entry) =>
                  filter === 'customer'
                      ? entry.audience === 'customer'
                      : filter === 'internal'
                        ? entry.audience === 'staff'
                        : entry.audience === 'system',
              );

    const chips: Array<{ key: FeedFilter; label: string; count: number }> = [
        { key: 'all', label: 'Everything', count: activity.counts.all },
        { key: 'customer', label: 'Customer', count: activity.counts.customer },
        { key: 'internal', label: 'Internal', count: activity.counts.internal },
        { key: 'changes', label: 'Changes', count: activity.counts.changes },
    ];

    const customerReply = visibility === 'customer';

    return (
        <SectionCard
            id="activity"
            title="Activity"
            description={
                activity.counts.all === 0
                    ? 'Everything said about this ticket, and every change to it, in one place.'
                    : `${activity.counts.all} ${
                          activity.counts.all === 1 ? 'entry' : 'entries'
                      }, oldest first — ${
                          activity.counts.customer
                      } with the customer, ${
                          activity.counts.internal
                      } internal${
                          activity.last_at
                              ? ` · last activity ${formatRelative(activity.last_at)}`
                              : ''
                      }.`
            }
            icon={MessageSquare}
            tone="info"
        >
            <div className="mb-4 flex flex-wrap items-center justify-between gap-3">
                <Participants people={discussion?.participants ?? []} />

                <div className="flex items-center gap-2">
                    {discussion && discussion.unread > 0 ? (
                        <span className="rounded-full bg-solar-soft px-2 py-0.5 text-[11px] font-medium text-solar-ink">
                            {discussion.unread} unread in the inbox
                        </span>
                    ) : null}

                    {discussion?.url ? (
                        <Button variant="outline" size="sm" asChild>
                            <Link href={discussion.url}>
                                <Inbox />
                                Open in inbox
                                <ArrowUpRight />
                            </Link>
                        </Button>
                    ) : null}
                </div>
            </div>

            <div className="mb-4 flex flex-wrap gap-1.5">
                {chips.map((chip) => (
                    <button
                        key={chip.key}
                        type="button"
                        onClick={() => setFilter(chip.key)}
                        aria-pressed={filter === chip.key}
                        data-test={`activity-filter-${chip.key}`}
                        className={cn(
                            'rounded-full px-2.5 py-1 text-xs font-medium ring-1 transition-colors',
                            filter === chip.key
                                ? 'bg-foreground text-background ring-foreground'
                                : 'text-muted-foreground ring-hairline hover:bg-accent/50 hover:text-foreground',
                        )}
                    >
                        {chip.label}
                        <span
                            className={cn(
                                'ml-1.5 tabular-nums',
                                filter === chip.key
                                    ? 'text-background/70'
                                    : 'text-muted-foreground/70',
                            )}
                        >
                            {chip.count}
                        </span>
                    </button>
                ))}
            </div>

            {entries.length === 0 ? (
                <EmptyState
                    icon={MessageSquare}
                    title={
                        activity.counts.all === 0
                            ? 'Nothing said yet'
                            : 'Nothing in this slice'
                    }
                    description={
                        activity.counts.all === 0
                            ? 'Write the first note, or record what the customer was told. Internal notes stay in the office; customer replies are part of the record.'
                            : 'Try another filter — the entries are still there.'
                    }
                />
            ) : (
                <ol className="flex flex-col gap-3">
                    {entries.map((entry) => (
                        <Entry key={entry.id} entry={entry} />
                    ))}
                </ol>
            )}

            {canComment ? (
                <div className="mt-5 border-t border-hairline pt-5">
                    <p className="text-sm font-medium">Add to this ticket</p>
                    <p className="mt-0.5 text-xs text-muted-foreground">
                        One box, two audiences. Pick who it is for first — the
                        entry is marked with that for good.
                    </p>

                    <div
                        className="mt-3 grid gap-2 sm:grid-cols-2"
                        role="radiogroup"
                        aria-label="Who is this for?"
                    >
                        <button
                            type="button"
                            role="radio"
                            aria-checked={visibility === 'internal'}
                            disabled={!canDiscuss}
                            onClick={() => setVisibility('internal')}
                            data-test="visibility-internal"
                            className={cn(
                                'flex items-start gap-2.5 rounded-lg border px-3 py-2.5 text-left transition-colors',
                                visibility === 'internal'
                                    ? 'border-amber-500/40 bg-amber-500/10'
                                    : 'border-hairline hover:bg-accent/40',
                                !canDiscuss && 'cursor-not-allowed opacity-50',
                            )}
                        >
                            <Lock className="mt-0.5 size-4 shrink-0 text-amber-600 dark:text-amber-400" />
                            <span className="min-w-0">
                                <span className="block text-sm font-medium">
                                    Internal note
                                </span>
                                <span className="mt-0.5 block text-xs text-muted-foreground">
                                    {canDiscuss
                                        ? 'Staff only. It is in the inbox too, with the people holding this ticket.'
                                        : 'You do not have the staff inbox, so this is not available to you.'}
                                </span>
                            </span>
                        </button>

                        <button
                            type="button"
                            role="radio"
                            aria-checked={visibility === 'customer'}
                            onClick={() => setVisibility('customer')}
                            data-test="visibility-customer"
                            className={cn(
                                'flex items-start gap-2.5 rounded-lg border px-3 py-2.5 text-left transition-colors',
                                visibility === 'customer'
                                    ? 'border-blue-500/40 bg-blue-500/10'
                                    : 'border-hairline hover:bg-accent/40',
                            )}
                        >
                            <Send className="mt-0.5 size-4 shrink-0 text-blue-600 dark:text-blue-400" />
                            <span className="min-w-0">
                                <span className="block text-sm font-medium">
                                    Customer reply
                                </span>
                                <span className="mt-0.5 block text-xs text-muted-foreground">
                                    What the customer said, or was told. Part of
                                    the record they can see.
                                </span>
                            </span>
                        </button>
                    </div>

                    <Form
                        action={
                            customerReply
                                ? TicketCommentController.store.url({
                                      ticket: ticket.id,
                                  })
                                : TicketDiscussionController.store.url({
                                      ticket: ticket.id,
                                  })
                        }
                        method="post"
                        options={{ preserveScroll: true, preserveState: false }}
                        className="mt-4 flex flex-col gap-4"
                    >
                        {({ processing, errors }) => (
                            <>
                                {customerReply ? (
                                    <input
                                        type="hidden"
                                        name="type"
                                        value="customer_reply"
                                    />
                                ) : null}

                                <FormField
                                    label={
                                        customerReply
                                            ? 'What the customer was told'
                                            : 'Note for the office'
                                    }
                                    htmlFor="activity-body"
                                    error={errors.body}
                                    hint={
                                        customerReply
                                            ? 'Recorded here as what the customer was told. Nothing is emailed from this screen.'
                                            : discussion &&
                                                discussion.to_add.length > 0
                                              ? `Private to staff. ${discussion.to_add.join(', ')} ${
                                                    discussion.to_add.length === 1
                                                        ? 'is'
                                                        : 'are'
                                                } holding this ticket and will be added too.`
                                              : 'Private to staff. Nothing written here is shown to the customer.'
                                    }
                                >
                                    <textarea
                                        id="activity-body"
                                        name="body"
                                        rows={3}
                                        required
                                        placeholder={
                                            customerReply
                                                ? 'e.g. Told the customer the inverter is being replaced under warranty and booked the crew for Thursday.'
                                                : 'e.g. Spoke to the customer — inverter is 40 minutes from here, asked the crew to attend Thursday.'
                                        }
                                        className={textareaClassName}
                                        data-test="activity-body"
                                    />
                                </FormField>

                                <FormField
                                    label="Attach a photo, screenshot or PDF"
                                    htmlFor="activity-attachment"
                                    error={
                                        errors.attachment ??
                                        errors['attachment.0']
                                    }
                                    hint="Optional — a fault code on the display saves a paragraph of explanation, whichever side it is for."
                                >
                                    <AttachmentPicker
                                        id="activity-attachment"
                                        name="attachment"
                                        multiple={false}
                                    />
                                </FormField>

                                <FormActions
                                    note={
                                        customerReply
                                            ? 'Customer replies are kept on the ticket; sending them by email is done from the inbox.'
                                            : 'Sent to the people holding this ticket, not the customer.'
                                    }
                                >
                                    <Button
                                        type="submit"
                                        disabled={processing}
                                        data-test="post-activity-button"
                                    >
                                        {customerReply ? (
                                            <Send />
                                        ) : (
                                            <Users />
                                        )}
                                        {processing
                                            ? 'Saving…'
                                            : customerReply
                                              ? 'Add customer reply'
                                              : 'Post internally'}
                                    </Button>
                                </FormActions>
                            </>
                        )}
                    </Form>
                </div>
            ) : (
                <p className="mt-5 border-t border-hairline pt-5 text-xs text-muted-foreground">
                    You can read this ticket but not add to it. Ask an
                    administrator for the tickets.update permission.
                </p>
            )}
        </SectionCard>
    );
}

/**
 * One entry in the feed.
 *
 * The colour and the badge carry the distinction, and they are the only thing
 * that differs between a note and a reply: same layout, same order, same place
 * to read them.
 */
function Entry({ entry }: { entry: TicketActivityEntry }) {
    const [open, setOpen] = useState(false);
    const tone = toneClasses(entry.tone);

    const remove = () => {
        if (entry.delete_url === null) {
            return;
        }

        if (!window.confirm('Remove this entry from the ticket?')) {
            return;
        }

        router.delete(entry.delete_url, { preserveScroll: true });
    };

    return (
        <li className="flex gap-3">
            <span
                aria-hidden
                className={cn(
                    'mt-0.5 flex size-8 shrink-0 items-center justify-center rounded-full text-[11px] font-semibold',
                    tone.soft,
                )}
            >
                {entry.kind === 'resolution' ? (
                    <CheckCircle2 className="size-4" />
                ) : entry.kind === 'change' ? (
                    <History className="size-3.5" />
                ) : (
                    initials(entry.author?.name ?? '?')
                )}
            </span>

            <div className="min-w-0 flex-1">
                <div className="flex flex-wrap items-center gap-x-2 gap-y-1">
                    <span className="text-sm font-medium">
                        {entry.kind === 'change'
                            ? 'Status change'
                            : entry.kind === 'resolution'
                              ? 'Resolved'
                              : entry.mine
                                ? 'You'
                                : (entry.author?.name ?? 'Unknown author')}
                    </span>

                    {entry.author?.role && entry.kind !== 'change' ? (
                        <span className="text-xs text-muted-foreground">
                            {entry.author.role}
                        </span>
                    ) : null}

                    {entry.kind !== 'change' ? (
                        <span
                            className={cn(
                                'rounded-full px-1.5 py-0.5 text-[10px] font-medium',
                                tone.pill,
                            )}
                        >
                            {entry.label}
                        </span>
                    ) : null}

                    <span
                        className="text-xs text-muted-foreground"
                        title={
                            entry.at
                                ? formatDateTime(entry.at)
                                : undefined
                        }
                    >
                        {formatRelative(entry.at)}
                    </span>

                    {entry.imported ? (
                        <span className="text-[10px] text-muted-foreground">
                            imported
                            {entry.legacy_id
                                ? ` · #${entry.legacy_id}`
                                : ''}
                        </span>
                    ) : null}

                    {entry.delete_url ? (
                        <button
                            type="button"
                            onClick={remove}
                            aria-label="Remove this entry"
                            className="ml-auto text-muted-foreground transition-colors hover:text-destructive"
                        >
                            <Trash2 className="size-3.5" />
                        </button>
                    ) : null}
                </div>

                {entry.kind === 'resolution' ? (
                    <p className="mt-1.5 flex flex-wrap items-center gap-x-1.5 gap-y-1 rounded-md border-l-2 border-success/60 bg-success-soft/50 px-3 py-2 text-sm text-muted-foreground">
                        <span className="font-medium text-foreground">
                            {entry.label}
                        </span>
                        {entry.author ? (
                            <span>signed off by {entry.author.name}</span>
                        ) : (
                            <span>recorded by the system it came from</span>
                        )}
                        <a
                            href="#outcome"
                            className="ml-auto text-xs font-medium text-success underline-offset-2 hover:underline"
                        >
                            View the outcome
                        </a>
                    </p>
                ) : (
                    <div
                        className={cn(
                            'relative mt-1.5 rounded-md py-2 pr-3 pl-4 ring-1',
                            entry.kind === 'change'
                                ? 'bg-muted/40 text-muted-foreground ring-hairline'
                                : 'ring-hairline',
                        )}
                    >
                        {/* The rail is the only difference in ink between a
                            note and a reply, which is the point: one feed, two
                            audiences, marked not separated. */}
                        <span
                            aria-hidden
                            className={cn(
                                'absolute inset-y-1.5 left-1.5 w-0.5 rounded-full',
                                tone.bar,
                            )}
                        />
                        {entry.body_html ? (
                            <RichText html={entry.body_html} />
                        ) : (
                            <p
                                className={cn(
                                    'text-sm leading-relaxed whitespace-pre-wrap',
                                    entry.kind === 'change' && 'italic',
                                )}
                            >
                                {entry.body}
                            </p>
                        )}
                    </div>
                )}

                {entry.attachment ? (
                    <div className="mt-2">
                        {entry.attachment.is_image ? (
                            <button
                                type="button"
                                onClick={() => setOpen(true)}
                                className="inline-block overflow-hidden rounded-md border border-hairline"
                                aria-label={`View ${entry.attachment.name}`}
                            >
                                <img
                                    src={entry.attachment.url}
                                    alt={entry.attachment.name}
                                    loading="lazy"
                                    className="max-h-48 max-w-full object-cover"
                                />
                            </button>
                        ) : null}

                        <p className="mt-1 flex flex-wrap items-center gap-x-2 text-xs">
                            <span className="text-muted-foreground">
                                Attachment:
                            </span>
                            <a
                                href={entry.attachment.url}
                                target="_blank"
                                rel="noreferrer"
                                className="font-medium underline underline-offset-2 hover:text-solar"
                            >
                                {entry.attachment.name}
                            </a>
                            <a
                                href={entry.attachment.download_url}
                                className="inline-flex items-center gap-1 text-muted-foreground underline underline-offset-2 hover:text-foreground"
                            >
                                <Download className="size-3" />
                                Download
                            </a>
                        </p>

                        {entry.attachment.is_image ? (
                            <AttachmentViewer
                                attachments={[
                                    {
                                        id: 0,
                                        name: entry.attachment.name,
                                        url: entry.attachment.url,
                                        download_url:
                                            entry.attachment.download_url,
                                        is_image: true,
                                    },
                                ]}
                                open={open}
                                onOpenChange={setOpen}
                            />
                        ) : null}
                    </div>
                ) : null}
            </div>
        </li>
    );
}
