/* eslint-disable @typescript-eslint/no-unused-vars */
/**
 * Angular wrapper for Bryntum ProjectModel
 */

import { Component, ElementRef, EventEmitter, Output, Input, SimpleChange, SimpleChanges, OnDestroy, OnInit } from '@angular/core';

import WrapperHelper from './wrapper.helper';

import { AssignmentModel, AssignmentModelConfig, AssignmentStore, AssignmentStoreConfig, DependencyModel, DependencyModelConfig, DependencyStore, DependencyStoreConfig, EventModel, EventModelConfig, EventStore, EventStoreConfig, Model, ModelConfig, ProjectModel, ResourceModel, ResourceModelConfig, ResourceStore, ResourceStoreConfig, ResourceTimeRangeModel, ResourceTimeRangeModelConfig, ResourceTimeRangeStore, ResourceTimeRangeStoreConfig, SchedulerProjectModel, SchedulerTimeRangeModel, StateTrackingManager, StateTrackingManagerConfig, Store, StoreConfig, TimeRangeModel, TimeRangeModelConfig, TimeSpan } from '@bryntum/calendar';

import { DomHelper, StringHelper } from '@bryntum/calendar';
import { initThemeInShadowRoots } from './shadowdom.helper';

export type BryntumCalendarProjectModelProps = {
    // Configs
    /**
     * A flag, indicating whether the dates and duration calculations should adjust the result to DST time shift.
     */
    adjustDurationToDST ? : boolean
    /**
     * The constructor of the assignment model class, to be used in the project. Will be set as the
     * [modelClass](https://bryntum.com/products/calendar/docs/api/Core/data/Store#config-modelClass) property of the [assignmentStore](#Scheduler/model/ProjectModel#property-assignmentStore)
     */
    assignmentModelClass ? : typeof AssignmentModel
    /**
     * Data use to fill the [assignmentStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#property-assignmentStore). Should be an array of
     * [AssignmentModels](https://bryntum.com/products/calendar/docs/api/Scheduler/model/AssignmentModel) or its configuration objects.
     */
    assignments ? : AssignmentModel[]|AssignmentModelConfig[]
    /**
     * The initial data, to fill the [assignmentStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#property-assignmentStore) with.
     * Should be an array of [AssignmentModels](https://bryntum.com/products/calendar/docs/api/Scheduler/model/AssignmentModel) or its configuration
     * objects.
     * @deprecated 6.3.0 Use [assignments](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#config-assignments) instead
     */
    assignmentsData ? : AssignmentModel[]|AssignmentModelConfig[]
    /**
     * An [AssignmentStore](https://bryntum.com/products/calendar/docs/api/Scheduler/data/AssignmentStore) instance or a config object.
     */
    assignmentStore ? : AssignmentStore|AssignmentStoreConfig
    /**
     * The constructor to create an assignment store instance with. Should be a class, subclassing the
     * [AssignmentStore](https://bryntum.com/products/calendar/docs/api/Scheduler/data/AssignmentStore)
     */
    assignmentStoreClass ? : typeof AssignmentStore|object
    /**
     * Child nodes. To allow loading children on demand, specify `children : true` in your data. Omit the field for leaf
     * tasks.
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#config-children)
     */
    children ? : boolean|object[]|Model[]|ModelConfig[]
    /**
     * Data use to fill the [dependencyStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#property-dependencyStore). Should be an array of
     * [DependencyModels](https://bryntum.com/products/calendar/docs/api/Scheduler/model/DependencyModel) or its configuration objects.
     */
    dependencies ? : DependencyModel[]|DependencyModelConfig[]
    /**
     * The initial data, to fill the [dependencyStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#property-dependencyStore) with.
     * Should be an array of [DependencyModels](https://bryntum.com/products/calendar/docs/api/Scheduler/model/DependencyModel) or its configuration
     * objects.
     * @deprecated 6.3.0 Use [dependencies](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#config-dependencies) instead
     */
    dependenciesData ? : DependencyModel[]|DependencyModelConfig[]
    /**
     * The constructor of the dependency model class, to be used in the project. Will be set as the
     * [modelClass](https://bryntum.com/products/calendar/docs/api/Core/data/Store#config-modelClass) property of the [dependencyStore](#Scheduler/model/ProjectModel#property-dependencyStore)
     */
    dependencyModelClass ? : typeof DependencyModel
    /**
     * A [DependencyStore](https://bryntum.com/products/calendar/docs/api/Scheduler/data/DependencyStore) instance or a config object.
     */
    dependencyStore ? : DependencyStore|DependencyStoreConfig
    /**
     * The constructor to create a dependency store instance with. Should be a class, subclassing the
     * [DependencyStore](https://bryntum.com/products/calendar/docs/api/Scheduler/data/DependencyStore)
     */
    dependencyStoreClass ? : typeof DependencyStore|object
    /**
     * The constructor of the event model class, to be used in the project. Will be set as the
     * [modelClass](https://bryntum.com/products/calendar/docs/api/Core/data/Store#config-modelClass) property of the [eventStore](#Scheduler/model/ProjectModel#property-eventStore)
     */
    eventModelClass ? : typeof EventModel
    /**
     * Data use to fill the [eventStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#property-eventStore). Should be an array of
     * [EventModels](https://bryntum.com/products/calendar/docs/api/Scheduler/model/EventModel) or its configuration objects.
     */
    events ? : EventModel[]|EventModelConfig[]
    /**
     * The initial data, to fill the [eventStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#property-eventStore) with.
     * Should be an array of [EventModels](https://bryntum.com/products/calendar/docs/api/Scheduler/model/EventModel) or its configuration objects.
     * @deprecated 6.3.0 Use [events](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#config-events) instead
     */
    eventsData ? : EventModel[]|EventModelConfig[]
    /**
     * An [EventStore](https://bryntum.com/products/calendar/docs/api/Scheduler/data/EventStore) instance or a config object.
     */
    eventStore ? : EventStore|EventStoreConfig
    /**
     * The constructor to create an event store instance with. Should be a class, subclassing the
     * [EventStore](https://bryntum.com/products/calendar/docs/api/Scheduler/data/EventStore)
     */
    eventStoreClass ? : typeof EventStore|object
    /**
     * Start expanded or not (only valid for tree data)
     */
    expanded ? : boolean
    /**
     * Unique identifier for the record. Might be mapped to another dataSource using idField, but always exposed as
     * record.id. Will get a generated value if none is specified in records data.
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#config-id)
     */
    id ? : string|number
    /**
     * Whether to include legacy data properties in the JSON / inlineData output. The legacy data properties are
     * the `xxData` (`eventsData`, `resourcesData` etc.) properties that are deprecated and will be removed in
     * the future.
     * @deprecated 6.3.0 This config will be removed when the eventsData, resourcesData etc. properties are removed in a future release.
     */
    includeLegacyDataProperties ? : boolean
    /**
     * This field is added to the class at runtime when the Store is configured with
     * [lazyLoad](https://bryntum.com/products/calendar/docs/api/Core/data/Store#config-lazyLoad). If set on a parent record at load time, that parent will not cause any
     * more child load requests. If omitted, it will be automatically set to `true` when a load request receives fewer
     * child records than requested.
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#config-isFullyLoaded)
     */
    isFullyLoaded ? : boolean
    /**
     * Project data as a JSON string, used to populate its stores.
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#config-json)
     */
    json ? : string
    /**
     * This is a read-only field provided in server synchronization packets to specify
     * which position the node takes in the parent's ordered children array.
     * This index is set on load and gets updated on reordering nodes in tree. Sorting and filtering
     * have no effect on it.
     */
    orderedParentIndex ? : number
    /**
     * This is a read-only field provided in server synchronization packets to specify
     * which record id is the parent of the record.
     */
    parentId ? : string|number|null
    /**
     * This is a read-only field provided in server synchronization packets to specify
     * which position the node takes in the parent's children array.
     * This index is set on load and gets updated automatically after row reordering, sorting, etc.
     * To save the order, need to persist the field on the server and when data is fetched to be loaded,
     * need to sort by this field.
     */
    parentIndex ? : number
    /**
     * Flag the record as read-only on the UI level, preventing the end user from manipulating it using editing
     * features such as cell editing and event dragging.
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#config-readOnly)
     */
    readOnly ? : boolean
    /**
     * This field is added to the class at runtime when the Store is configured with
     * [lazyLoad](https://bryntum.com/products/calendar/docs/api/Core/data/Store#config-lazyLoad). The
     * number specified should reflect the <strong>total</strong> amount of children of a parent node, including nested descendants.
     * @deprecated This field has been deprecated. Please read the [guide](https://bryntum.com/products/calendar/docs/api/Grid/guides/data/lazyloading.md) to find out if your app needs to use the new [isFullyLoaded](https://bryntum.com/products/calendar/docs/api/Core/data/mixin/TreeNode#field-isFullyLoaded) field.
     */
    remoteChildCount ? : number
    /**
     * The constructor of the resource model class, to be used in the project. Will be set as the
     * [modelClass](https://bryntum.com/products/calendar/docs/api/Core/data/Store#config-modelClass) property of the [resourceStore](#Scheduler/model/ProjectModel#property-resourceStore)
     */
    resourceModelClass ? : typeof ResourceModel
    /**
     * Data use to fill the [resourceStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#property-resourceStore). Should be an array of
     * [ResourceModels](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ResourceModel) or its configuration objects.
     */
    resources ? : ResourceModel[]|ResourceModelConfig[]
    /**
     * The initial data, to fill the [resourceStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#property-resourceStore) with.
     * Should be an array of [ResourceModels](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ResourceModel) or its configuration objects.
     * @deprecated 6.3.0 Use [resources](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#config-resources) instead
     */
    resourcesData ? : ResourceModel[]|ResourceModelConfig[]
    /**
     * A [ResourceStore](https://bryntum.com/products/calendar/docs/api/Scheduler/data/ResourceStore) instance or a config object.
     */
    resourceStore ? : ResourceStore|ResourceStoreConfig
    /**
     * The constructor to create a resource store instance with. Should be a class, subclassing the
     * [ResourceStore](https://bryntum.com/products/calendar/docs/api/Scheduler/data/ResourceStore)
     */
    resourceStoreClass ? : typeof ResourceStore|object
    /**
     * Data use to fill the [resourceTimeRangeStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#property-resourceTimeRangeStore). Should be an array
     * of [ResourceTimeRangeModels](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ResourceTimeRangeModel) or its configuration objects.
     */
    resourceTimeRanges ? : ResourceTimeRangeModel[]|ResourceTimeRangeModelConfig[]
    /**
     * The initial data, to fill the [resourceTimeRangeStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/mixin/ProjectModelMixin#property-resourceTimeRangeStore) with.
     * Should be an array of [ResourceTimeRangeModel](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ResourceTimeRangeModel) or it's
     * configuration objects.
     * @deprecated 6.3.0 Use resourceTimeRanges instead
     */
    resourceTimeRangesData ? : ResourceTimeRangeModel[]
    /**
     * A [ResourceTimeRangeStore](https://bryntum.com/products/calendar/docs/api/Scheduler/data/ResourceTimeRangeStore) instance or a config object.
     */
    resourceTimeRangeStore ? : ResourceTimeRangeStore|ResourceTimeRangeStoreConfig
    /**
     * The constructor to create a resource time range store instance with. Should be a class subclassing the
     * [ResourceTimeRangeStore](https://bryntum.com/products/calendar/docs/api/Scheduler/data/ResourceTimeRangeStore)
     */
    resourceTimeRangeStoreClass ? : typeof ResourceTimeRangeStore|object
    /**
     * Experimental hook that lets the app determine if a bound dataset needs syncing with the store or not, and
     * if it does - which records that should be processed. Only called for stores that are configured with
     * `syncDataOnLoad: true` (which is the default in the React, Angular and Vue wrappers).
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#config-shouldSyncDataOnLoad)
     * @param {object} options Options passed by the store to this hook
     * @param {Core.data.Store} options.store Store about to be synced
     * @param {Core.data.Model} options.records Records currently in the store
     * @param {object[]} options.data Incoming data
     * @returns {Set<any>,boolean} Return `false` to prevent the store from syncing, or a set of record ids that need further processing (for records that has some kind of change, eg. an update, removal or addition)
     */
    shouldSyncDataOnLoad ? : (options: { store: Store, records: Model, data: object[] }) => Set<any>|boolean
    /**
     * Silences propagations caused by the project loading.
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#config-silenceInitialCommit)
     */
    silenceInitialCommit ? : boolean
    /**
     * Configuration options to provide to the STM manager
     */
    stm ? : StateTrackingManagerConfig|StateTrackingManager
    /**
     * The constructor of the time range model class, to be used in the project. Will be set as the
     * [modelClass](https://bryntum.com/products/calendar/docs/api/Core/data/Store#config-modelClass) property of the [timeRangeStore](#Calendar/model/ProjectModel#property-timeRangeStore)
     */
    timeRangeModelClass ? : typeof TimeRangeModel
    /**
     * Data use to fill the [timeRangeStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/ProjectModel#property-timeRangeStore). Should be an array of
     * [TimeRangeModels](https://bryntum.com/products/calendar/docs/api/Scheduler/model/TimeRangeModel) or its configuration objects.
     */
    timeRanges ? : SchedulerTimeRangeModel[]|TimeRangeModelConfig[]
    /**
     * The initial data, to fill the [timeRangeStore](https://bryntum.com/products/calendar/docs/api/Scheduler/model/mixin/ProjectModelMixin#property-timeRangeStore) with.
     * Should be an array of [TimeSpan](https://bryntum.com/products/calendar/docs/api/Scheduler/model/TimeSpan) or its configuration objects.
     * @deprecated 6.3.0 Use timeRanges instead
     */
    timeRangesData ? : TimeSpan[]
    /**
     * A [Store](https://bryntum.com/products/calendar/docs/api/Core/data/Store) instance or a config object.
     */
    timeRangeStore ? : Store|StoreConfig
    /**
     * The constructor to create an timeRange store instance with. Should be a class, subclassing the
     * [Store](https://bryntum.com/products/calendar/docs/api/Core/data/Store)
     */
    timeRangeStoreClass ? : typeof Store|object
    /**
     * Set to a IANA time zone (i.e. `Europe/Stockholm`) or a UTC offset in minutes (i.e. `-120`). This will
     * convert all events, tasks and time ranges to the specified time zone or offset. It will also affect the
     * displayed timeline's headers as well at the start and end date of it.
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#config-timeZone)
     */
    timeZone ? : string|number
    /**
     * Specifies the output format of [toJSON](https://bryntum.com/products/calendar/docs/api/Scheduler/model/mixin/ProjectModelCommon#function-toJSON).
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#config-toJSONResultFormat)
     */
    toJSONResultFormat ? : 'inlineData'|'model'
    /**
     * By default, the stores of a project use the raw data objects passed to them as the data source for their
     * records if data is loaded remotely (using an `AjaxStore` or a `CrudManager`). For data supplied inline,
     * the data objects are instead by default cloned to avoid the original data object being modified by the
     * store.
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#config-useRawData)
     */
    useRawData ? : boolean

}

@Component({
    selector : 'bryntum-calendar-project-model',
    template : ''
})
export class BryntumCalendarProjectModelComponent implements OnInit, OnDestroy {

    public static instanceClass = ProjectModel;

    public static instanceName = 'ProjectModel';

    private static bryntumEvents: string[] = [
        'onChange',
        'onDataReady'
    ];

    private static bryntumFeatureNames: string[] = [

    ];

    private static bryntumConfigs: string[] = BryntumCalendarProjectModelComponent.bryntumFeatureNames.concat([
        'adjustDurationToDST',
        'assignmentModelClass',
        'assignments',
        'assignmentsData',
        'assignmentStore',
        'assignmentStoreClass',
        'children',
        'dependencies',
        'dependenciesData',
        'dependencyModelClass',
        'dependencyStore',
        'dependencyStoreClass',
        'eventModelClass',
        'events',
        'eventsData',
        'eventStore',
        'eventStoreClass',
        'expanded',
        'id',
        'includeLegacyDataProperties',
        'isFullyLoaded',
        'json',
        'orderedParentIndex',
        'parentId',
        'parentIndex',
        'readOnly',
        'remoteChildCount',
        'resourceModelClass',
        'resources',
        'resourcesData',
        'resourceStore',
        'resourceStoreClass',
        'resourceTimeRanges',
        'resourceTimeRangesData',
        'resourceTimeRangeStore',
        'resourceTimeRangeStoreClass',
        'shouldSyncDataOnLoad',
        'silenceInitialCommit',
        'stm',
        'timeRangeModelClass',
        'timeRanges',
        'timeRangesData',
        'timeRangeStore',
        'timeRangeStoreClass',
        'timeZone',
        'toJSONResultFormat',
        'useRawData'
    ]);

    private static bryntumConfigsOnly: string[] = [
        'adjustDurationToDST',
        'assignmentModelClass',
        'assignmentsData',
        'assignmentStoreClass',
        'children',
        'dependenciesData',
        'dependencyModelClass',
        'dependencyStoreClass',
        'eventModelClass',
        'eventsData',
        'eventStoreClass',
        'expanded',
        'orderedParentIndex',
        'parentIndex',
        'resourceModelClass',
        'resourcesData',
        'resourceStoreClass',
        'resourceTimeRangesData',
        'resourceTimeRangeStoreClass',
        'silenceInitialCommit',
        'timeRangeModelClass',
        'timeRangesData',
        'timeRangeStoreClass',
        'toJSONResultFormat',
        'useRawData'
    ];

    private static bryntumProps: string[] = BryntumCalendarProjectModelComponent.bryntumFeatureNames.concat([
        'assignments',
        'assignmentStore',
        'dependencies',
        'dependencyStore',
        'descendantCount',
        'events',
        'eventStore',
        'id',
        'includeLegacyDataProperties',
        'inlineData',
        'isCreating',
        'isFullyLoaded',
        'json',
        'parentId',
        'readOnly',
        'remoteChildCount',
        'resources',
        'resourceStore',
        'resourceTimeRanges',
        'resourceTimeRangeStore',
        'shouldSyncDataOnLoad',
        'stm',
        'timeRanges',
        'timeRangeStore',
        'timeZone',
        'visibleDescendantCount'
    ]);

    private elementRef: ElementRef;
    public instance!: ProjectModel;

    private bryntumConfig = {
        adopt            : undefined,
        appendTo         : undefined,
        href             : undefined,
        angularComponent : this,
        features         : {},
        listeners        : {}
    };

    constructor(element: ElementRef) {
        this.elementRef = element;
    }

    // Configs only
    @Input() adjustDurationToDST         ! : boolean;
    @Input() assignmentModelClass        ! : typeof AssignmentModel;
    @Input() assignmentsData             ! : AssignmentModel[]|AssignmentModelConfig[];
    @Input() assignmentStoreClass        ! : typeof AssignmentStore|object;
    @Input() children                    ! : boolean|object[]|Model[]|ModelConfig[];
    @Input() dependenciesData            ! : DependencyModel[]|DependencyModelConfig[];
    @Input() dependencyModelClass        ! : typeof DependencyModel;
    @Input() dependencyStoreClass        ! : typeof DependencyStore|object;
    @Input() eventModelClass             ! : typeof EventModel;
    @Input() eventsData                  ! : EventModel[]|EventModelConfig[];
    @Input() eventStoreClass             ! : typeof EventStore|object;
    @Input() expanded                    ! : boolean;
    @Input() orderedParentIndex          ! : number;
    @Input() parentIndex                 ! : number;
    @Input() resourceModelClass          ! : typeof ResourceModel;
    @Input() resourcesData               ! : ResourceModel[]|ResourceModelConfig[];
    @Input() resourceStoreClass          ! : typeof ResourceStore|object;
    @Input() resourceTimeRangesData      ! : ResourceTimeRangeModel[];
    @Input() resourceTimeRangeStoreClass ! : typeof ResourceTimeRangeStore|object;
    @Input() silenceInitialCommit        ! : boolean;
    @Input() timeRangeModelClass         ! : typeof TimeRangeModel;
    @Input() timeRangesData              ! : TimeSpan[];
    @Input() timeRangeStoreClass         ! : typeof Store|object;
    @Input() toJSONResultFormat          ! : 'inlineData'|'model';
    @Input() useRawData                  ! : boolean;

    // Configs and properties
    @Input() assignments                 ! : AssignmentModel[]|AssignmentModelConfig[];
    @Input() assignmentStore             ! : AssignmentStore|AssignmentStoreConfig;
    @Input() dependencies                ! : DependencyModel[]|DependencyModelConfig[];
    @Input() dependencyStore             ! : DependencyStore|DependencyStoreConfig;
    @Input() events                      ! : EventModel[]|EventModelConfig[];
    @Input() eventStore                  ! : EventStore|EventStoreConfig;
    @Input() id                          ! : string|number;
    @Input() includeLegacyDataProperties ! : boolean;
    @Input() isFullyLoaded               ! : boolean;
    @Input() json                        ! : string;
    @Input() parentId                    ! : number|string|null;
    @Input() readOnly                    ! : boolean;
    @Input() remoteChildCount            ! : number;
    @Input() resources                   ! : ResourceModel[]|ResourceModelConfig[];
    @Input() resourceStore               ! : ResourceStore|ResourceStoreConfig;
    @Input() resourceTimeRanges          ! : ResourceTimeRangeModel[]|ResourceTimeRangeModelConfig[];
    @Input() resourceTimeRangeStore      ! : ResourceTimeRangeStore|ResourceTimeRangeStoreConfig;
    @Input() shouldSyncDataOnLoad        ! : (options: { store: Store, records: Model, data: object[] }) => Set<any>|boolean;
    @Input() stm                         ! : StateTrackingManager|StateTrackingManagerConfig;
    @Input() timeRanges                  ! : SchedulerTimeRangeModel[]|TimeRangeModelConfig[];
    @Input() timeRangeStore              ! : Store|StoreConfig;
    @Input() timeZone                    ! : string|number;

    // Properties only
    @Input() descendantCount        ! : number;
    @Input() inlineData             ! : object;
    @Input() isCreating             ! : boolean;
    @Input() visibleDescendantCount ! : number;

    // Events emitters
    /**
     * Fired when data in any of the projects stores changes.
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#event-change)
     * @param {object} event Event object
     * @param {Scheduler.model.SchedulerProjectModel,any} event.source This project
     * @param {Core.data.Store} event.store Affected store
     * @param {'remove','removeAll','add','clearchanges','filter','update','dataset','replace'} event.action Name of action which triggered the change. May be one of:  * `'remove'` * `'removeAll'` * `'add'` * `'clearchanges'` * `'filter'` * `'update'` * `'dataset'` * `'replace'`
     * @param {Core.data.Model} event.record Changed record, for actions that affects exactly one record (`'update'`)
     * @param {Core.data.Model[]} event.records Changed records, passed for all actions except `'removeAll'`
     * @param {object} event.changes Passed for the `'update'` action, info on which record fields changed
     */
    @Output() onChange: any = new EventEmitter<((event: { source: SchedulerProjectModel|any, store: Store, action: 'remove'|'removeAll'|'add'|'clearchanges'|'filter'|'update'|'dataset'|'replace', record: Model, records: Model[], changes: object }) => void)|string>();
    /**
     * Fired when the engine has finished its calculations and the results has been written back to the records.
     * [More...](https://bryntum.com/products/calendar/docs/api/Calendar/model/ProjectModel#event-dataReady)
     * @param {object} event Event object
     * @param {Scheduler.model.SchedulerProjectModel,any} event.source The project
     * @param {boolean} event.isInitialCommit Flag that shows if this commit is initial
     * @param {Set<any>} event.records Set of all [Model](https://bryntum.com/products/calendar/docs/api/Core/data/Model)s that were modified in the completed transaction. Use the [modifications](https://bryntum.com/products/calendar/docs/api/Core/data/Model#property-modifications) property of each Model to identify modified fields.
     */
    @Output() onDataReady: any = new EventEmitter<((event: { source: SchedulerProjectModel|any, isInitialCommit: boolean, records: Set<any> }) => void)|string>();

    /**
     * Create and append the underlying widget
     */
    ngOnInit(): void {
        const
            me = this,
            {
                elementRef,
                bryntumConfig
            } = me,
            {
                instanceClass,
                instanceName,
                bryntumConfigs,
                bryntumEvents
            } = BryntumCalendarProjectModelComponent;

        bryntumConfigs.filter(prop => prop in this).forEach(prop => {
            // @ts-ignore
            WrapperHelper.applyPropValue(bryntumConfig, prop, this[prop]);
            if (['features', 'config'].includes(prop)) {
                WrapperHelper.devWarningConfigProp(instanceName, prop);
            }
        });
        // @ts-ignore
        bryntumEvents.filter(event => this[event] && this[event].observers.length > 0).forEach(event => {
            const
                uncapitalize = (str: string) => str.charAt(0).toLowerCase() + str.slice(1),
                eventName = (str: string) => uncapitalize(str.slice(2));

            // @ts-ignore
            bryntumConfig.listeners[eventName(event)] = e => {
                // @ts-ignore
                me[event].emit(e);
                // EventEmitter does not return values in the normal way, work around it by setting `returnValue` flag
                // in Angular listeners
                return e.returnValue;
            };
        });

        // If component has no container specified in config then use adopt to Wrapper's element
        const
            containerParam = [
                'adopt',
                'appendTo',
                'insertAfter',
                'insertBefore'
                // @ts-ignore
            ].find(prop => bryntumConfig[prop]);
        if (!containerParam) {
            if (instanceName === 'Button' || elementRef.nativeElement.getRootNode() instanceof ShadowRoot) {
                // Button should always be <a> or <button> inside owner element
                bryntumConfig.appendTo = elementRef.nativeElement;
            }
            else {
                bryntumConfig.adopt = elementRef.nativeElement;
            }
        }
        else {
            WrapperHelper.devWarningContainer(instanceName, containerParam);
        }

        // In shadow DOM (e.g. Angular ViewEncapsulation.ShadowDom), theme CSS from document.head
        // does not cascade into the shadow root. Inject a matching <link> so Bryntum's CSS check
        // finds the theme. The @font-face rules from component styles are also extracted to
        // document scope so document.fonts detects them (shadow-root @font-face is not reliably
        // included in document.fonts across all browsers).
        const shadowRoot = elementRef.nativeElement.getRootNode();
        if (shadowRoot instanceof ShadowRoot) {
            initThemeInShadowRoots();
            BryntumCalendarProjectModelComponent.ensureFontsInDocument(shadowRoot);
        }

        // @ts-ignore
        me.instance = instanceName === 'Widget' ? Widget.create(bryntumConfig) : new instanceClass(bryntumConfig);

    }

    /**
     * Extracts all @font-face declarations from a shadow root's stylesheets and adds them to
     * document.head as a single <style> element. This is needed because @font-face rules inside
     * a shadow root are not reliably included in document.fonts across all browsers, causing
     * Bryntum's CSS compatibility check to incorrectly report missing fonts.
     * Safe to call multiple times — the extraction runs only once per page.
     */
    private static ensureFontsInDocument(shadowRoot: ShadowRoot): void {
        if (document.querySelector('#b-shadow-root-fonts')) {
            return;
        }

        const fontFaceRules: string[] = [];

        const extractFromSheet = (sheet: CSSStyleSheet): void => {
            try {
                const rules = sheet.cssRules;
                for (let i = 0; i < rules.length; i++) {
                    if (rules[i].type === CSSRule.FONT_FACE_RULE) {
                        fontFaceRules.push(rules[i].cssText);
                    }
                }
            }
            catch (_e) {
                // Cross-origin access may throw; silently skip
            }
        };

        // adoptedStyleSheets (Angular 14+ / modern browsers)
        const adoptedSheets: CSSStyleSheet[] = (shadowRoot as any).adoptedStyleSheets ?? [];
        adoptedSheets.forEach(sheet => extractFromSheet(sheet));

        // <style> elements (older Angular or fallback)
        shadowRoot.querySelectorAll('style').forEach(el => {
            if (el.sheet) {
                extractFromSheet(el.sheet);
            }
        });

        if (fontFaceRules.length > 0) {
            const style = document.createElement('style');
            style.id = 'b-shadow-root-fonts';
            style.textContent = fontFaceRules.join('\n');
            document.head.appendChild(style);
        }
    }

    /**
     * Watch for changes
     * @param changes
     */
    ngOnChanges(changes: SimpleChanges): void {
        const
            { instance } = this,
            { instanceName } = BryntumCalendarProjectModelComponent;
        if (!instance) {
            return;
        }
        // Iterate over all changes
        Object.entries(changes).forEach(([prop, change]) => {
            const
                newValue = (change as SimpleChange).currentValue,
                { instance } = this,
                { bryntumConfigsOnly, bryntumProps } = BryntumCalendarProjectModelComponent;
            if (bryntumProps.includes(prop)) {
                WrapperHelper.applyPropValue(instance, prop, newValue, false);
                if (bryntumConfigsOnly.includes(prop)) {
                    WrapperHelper.devWarningUpdateProp(instanceName, prop);
                }
            }
        });
    }

    /**
     * Destroy the component
     */
    ngOnDestroy(): void {
        // @ts-ignore
        if (this.instance && this.instance.destroy) {
            this.instance.destroy();
        }
    }
}
