import { CollectionConfig } from 'payload'
import { isPublishedPublic } from '@/collections/access/public'
import {
  canCreateAssignedEvent,
  canMutateAssignedEvent,
  defaultToOnlyAssigned,
  filterEventsToAssigned,
  getAssignedIds,
  hasAssignedMatch,
  hideUnlessAssignedAny,
  toIdArray,
  unassignedAdditions,
} from '@/collections/access/assigned'
import { regenerateOccurrencesForEvent } from '@/jobs/generateEventOccurrences'
import { revalidateTagHook } from '@/utils/revalidate'

export const Events: CollectionConfig = {
  slug: 'event',
  labels: {
    singular: {
      de: 'Veranstaltung',
    },
    plural: {
      de: 'Veranstaltungen',
    },
  },
  fields: [
    {
      type: 'tabs',
      tabs: [
        {
          label: { de: 'Allgemein' },
          fields: [
            {
              name: 'title',
              type: 'text',
              required: true,
              label: {
                de: 'Titel',
              },
            },
            {
              name: 'date',
              type: 'date',
              required: true,
              label: {
                de: 'Datum',
              },
              admin: {
                date: {
                  pickerAppearance: 'dayAndTime',
                  timeIntervals: 15,
                  timeFormat: 'HH:mm'
                },
              },
            },
            {
              name: 'endDateTime',
              type: 'date',
              label: {
                de: 'Enduhrzeit',
              },
              admin: {
                date: {
                  pickerAppearance: 'dayAndTime',
                  timeIntervals: 15,
                  timeFormat: 'HH:mm',
                },
              },
            },
            {
              name: 'cancelled',
              type: 'checkbox',
              required: true,
              label: {
                de: 'Abgesagt',
              },
              defaultValue: false,
            },
            {
              name: 'location',
              type: 'relationship',
              relationTo: 'locations',
              required: true,
              label: {
                de: 'Location'
              },
            },
            {
              name: 'shortDescription',
              type: 'textarea',
              required: true,
              label: {
                de: 'Kurzumschreibung (max. 200)',
              },
              maxLength: 200
            },
            {
              name: 'description',
              type: 'textarea',
              required: true,
              label: {
                de: 'Einladung',
              },
            },
            {
              name: 'contact',
              type: 'relationship',
              relationTo: 'contactPerson',
              label: {
                de: "Ansprechperson"
              }
            },
            {
              name: 'rsvpLink',
              type: 'text',
              required: false,
              label: {
                de: "Anmeldelink"
              }
            },
          ],
        },
        {
          label: { de: 'Zuordnung' },
          fields: [
            {
              name: 'parish',
              type: 'relationship',
              relationTo: 'parish',
              hasMany: true,
              label: {
                de: 'Gemeinde',
              },
              defaultValue: defaultToOnlyAssigned('parishes'),
              // A Gläubiger may only ADD their own parishes — unless they are
              // assigned to a group; group members may attach any parish.
              // Connections already on the doc may always be kept.
              validate: (value: unknown, options: any) => {
                const user = options.req?.user
                if (!user || user.roles !== 'user') return true
                if (getAssignedIds(user, 'groups').length > 0) return true
                if (unassignedAdditions(user, 'parishes', value, options.previousValue).length > 0) {
                  return 'Sie können nur Gemeinden hinzufügen, die Ihnen zugewiesen sind.'
                }
                return true
              },
            },
            {
              name: 'group',
              type: 'relationship',
              relationTo: 'group',
              hasMany: true,
              label: {
                de: 'Gruppe',
              },
              defaultValue: defaultToOnlyAssigned('groups'),
              // Combined rule for parish AND group (validated once, here):
              // a Gläubiger's event needs at least one connection, and at
              // least one of them must be an own assignment. Additional
              // foreign connections are allowed.
              validate: (value: unknown, options: any) => {
                const user = options.req?.user

                // No user = local API (jobs, scripts); unauthenticated REST
                // writes are already blocked by the collection access.
                if (!user || user.roles !== 'user') {
                  return true
                }

                const groupIds = toIdArray(value)
                const parishIds = toIdArray(options.data?.parish)

                // Parish-assigned users may attach any group; otherwise only own groups may be added.
                if (
                  getAssignedIds(user, 'parishes').length === 0 &&
                  unassignedAdditions(user, 'groups', value, options.previousValue).length > 0
                ) {
                  return 'Sie können nur Gruppen hinzufügen, die Ihnen zugewiesen sind.'
                }

                if (groupIds.length === 0 && parishIds.length === 0) {
                  return 'Sie müssen die Veranstaltung mit mindestens einer Gruppe oder Gemeinde verknüpfen.'
                }

                if (
                  !hasAssignedMatch(user, groupIds, 'groups') &&
                  !hasAssignedMatch(user, parishIds, 'parishes')
                ) {
                  return 'Mindestens eine der ausgewählten Gruppen oder Gemeinden muss Ihnen zugewiesen sein.'
                }

                return true
              },
            },
          ],
        },
        {
          label: { de: 'Medien' },
          fields: [
            {
              name: 'photo',
              label: {
                de: 'Foto',
              },
              type: 'upload',
              relationTo: 'media',
            },
            {
              name: 'flyer',
              label: {
                de: "Flyer (PDF)"
              },
              type: 'upload',
              relationTo: 'documents'
            },
          ],
        },
        {
          label: { de: 'Wiederholung' },
          fields: [
            {
              name: 'recurrenceType',
              type: 'select',
              required: true,
              defaultValue: 'none',
              label: {
                de: 'Wiederholung',
              },
              options: [
                { label: 'Einmalig', value: 'none' },
                { label: 'Täglich', value: 'daily' },
                { label: 'Wöchentlich', value: 'weekly' },
                { label: 'Alle 2 Wochen', value: 'biweekly' },
                { label: 'Monatlich (gleicher Kalendertag)', value: 'monthlyByDate' },
                { label: 'Monatlich (n-ter Wochentag)', value: 'monthlyByWeekday' },
                { label: 'Benutzerdefiniert (mehrere Regeln)', value: 'custom' },
              ],
              admin: {
                description:
                  'Bei wiederkehrenden Veranstaltungen werden automatisch Termine für die nächsten Wochen erzeugt. Wochentag, Kalendertag und Uhrzeit werden aus dem Datumsfeld übernommen.',
                components: {
                  Field:
                    '@/admin/components/RecurrenceTypeField/RecurrenceTypeField#RecurrenceTypeField',
                },
              },
            },
            {
              name: 'recurrenceRules',
              type: 'array',
              label: { de: 'Wiederholungsregeln' },
              labels: {
                singular: { de: 'Regel' },
                plural: { de: 'Regeln' },
              },
              admin: {
                condition: (_data, siblingData) =>
                  siblingData?.recurrenceType === 'custom',
                description:
                  'Mehrere Regeln werden kombiniert (z. B. „3. Dienstag und 4. Donnerstag jeden Monat").',
              },
              validate: (value, { siblingData }: { siblingData: any }) => {
                if (siblingData?.recurrenceType !== 'custom') return true
                if (!Array.isArray(value) || value.length === 0) {
                  return 'Mindestens eine Regel erforderlich, wenn Wiederholung = Benutzerdefiniert.'
                }
                const rules = value as Array<{
                  frequency?: string
                  weekday?: string | null
                  weekOfMonth?: string | null
                  dayOfMonth?: number | null
                }>
                for (let i = 0; i < rules.length; i++) {
                  const r = rules[i]
                  if (r?.frequency === 'weekly' && !r.weekday) {
                    return `Regel ${i + 1}: Wochentag fehlt.`
                  }
                  if (r?.frequency === 'monthlyByWeekday' && (!r.weekday || !r.weekOfMonth)) {
                    return `Regel ${i + 1}: Wochentag und Position im Monat sind erforderlich.`
                  }
                  if (r?.frequency === 'monthlyByDate' && typeof r.dayOfMonth !== 'number') {
                    return `Regel ${i + 1}: Kalendertag fehlt.`
                  }
                }
                return true
              },
              fields: [
                {
                  name: 'frequency',
                  type: 'select',
                  required: true,
                  label: { de: 'Art' },
                  options: [
                    { label: 'Täglich', value: 'daily' },
                    { label: 'Wöchentlich', value: 'weekly' },
                    { label: 'Monatlich (Kalendertag)', value: 'monthlyByDate' },
                    { label: 'Monatlich (n-ter Wochentag)', value: 'monthlyByWeekday' },
                  ],
                },
                {
                  name: 'weekday',
                  type: 'select',
                  label: { de: 'Wochentag' },
                  options: [
                    { label: 'Montag', value: 'monday' },
                    { label: 'Dienstag', value: 'tuesday' },
                    { label: 'Mittwoch', value: 'wednesday' },
                    { label: 'Donnerstag', value: 'thursday' },
                    { label: 'Freitag', value: 'friday' },
                    { label: 'Samstag', value: 'saturday' },
                    { label: 'Sonntag', value: 'sunday' },
                  ],
                  admin: {
                    condition: (_data, siblingData) =>
                      siblingData?.frequency === 'weekly' ||
                      siblingData?.frequency === 'monthlyByWeekday',
                  },
                },
                {
                  name: 'weekOfMonth',
                  type: 'select',
                  label: { de: 'Position im Monat' },
                  options: [
                    { label: '1.', value: 'first' },
                    { label: '2.', value: 'second' },
                    { label: '3.', value: 'third' },
                    { label: '4.', value: 'fourth' },
                    { label: '5.', value: 'fifth' },
                    { label: 'Letzter', value: 'last' },
                  ],
                  admin: {
                    condition: (_data, siblingData) =>
                      siblingData?.frequency === 'monthlyByWeekday',
                  },
                },
                {
                  name: 'dayOfMonth',
                  type: 'number',
                  label: { de: 'Kalendertag' },
                  min: 1,
                  max: 31,
                  admin: {
                    condition: (_data, siblingData) =>
                      siblingData?.frequency === 'monthlyByDate',
                  },
                },
              ],
            },
            {
              name: 'endDate',
              type: 'date',
              label: {
                de: 'Enddatum',
              },
              admin: {
                date: {
                  pickerAppearance: 'dayOnly',
                },
                description: 'Optional. Nach diesem Datum werden keine weiteren Termine erzeugt.',
              },
            },
          ],
        },
        {
          label: { de: 'Nächste Termine' },
          fields: [
            {
              name: 'nextOccurrences',
              type: 'ui',
              admin: {
                components: {
                  Field: {
                    path: '@/admin/components/NextOccurrencesTable/NextOccurrencesTable#NextOccurrencesTable',
                  },
                },
              },
            },
          ],
        },
      ],
    },
  ],
  admin: {
    useAsTitle: 'title',
    hidden: hideUnlessAssignedAny(['groups', 'parishes']),
    baseFilter: filterEventsToAssigned(),
    livePreview: {
      url: ({ data }) => `/api/draft?url=/veranstaltungen/${data.id}`,
    },
  },
  versions: {
    drafts: {
      validate: true
    },
  },
  access: {
    read: isPublishedPublic(),
    create: canCreateAssignedEvent(),
    update: canMutateAssignedEvent(),
    delete: canMutateAssignedEvent(),
  },
  hooks: {
    afterChange: [
      async ({ doc, previousDoc, req }) => {
        try {
          await regenerateOccurrencesForEvent({
            event: doc,
            payload: req.payload,
            req,
          })

          // Propagate event.cancelled to all future occurrences when it flips,
          // so toggling the event-level flag acts as a master switch. Skipped
          // when unchanged so unrelated saves don't overwrite per-occurrence
          // cancellations set from the "Nächste Termine" tab.
          if (doc.cancelled !== previousDoc?.cancelled) {
            await req.payload.update({
              collection: 'eventOccurrence',
              where: {
                and: [
                  { event: { equals: doc.id } },
                  { date: { greater_than_equal: new Date().toISOString() } },
                ],
              },
              data: { cancelled: doc.cancelled },
              req,
            })
          }
        } catch (err) {
          req.payload.logger.error(
            { err, eventId: doc.id },
            'Failed to regenerate event occurrences',
          )
        }
      },

      revalidateTagHook('events'),
    ],
    beforeDelete: [
      async ({ id, req }) => {
        // Cascade before the event row is removed so the FK from
        // eventOccurrence.event (required) doesn't abort the transaction.
        await req.payload.delete({
          collection: 'eventOccurrence',
          where: { event: { equals: id } },
          req,
        })
      },
    ],
  },
}

