using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditorInternal;
#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
#else
using UnityEditor.Experimental.SceneManagement;
#endif
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
#if !UNITY_2020_2_OR_NEWER
using UnityEngine.Experimental.Animations;
#endif
using UnityEngine.Animations;
using Object = System.Object;
namespace UnityEditor.Timeline
{
    delegate bool PendingUpdateDelegate(WindowState state, Event currentEvent);
    /// 
    /// Interface for faking purposes
    /// 
    interface IWindowState
    {
        ISequenceState masterSequence { get; }
        ISequenceState editSequence { get; }
        IEnumerable allSequences { get; }
        PlayRange playRange { get; set; }
        void SetCurrentSequence(TimelineAsset timelineAsset, PlayableDirector director, TimelineClip hostClip);
        void PopSequencesUntilCount(int count);
        IEnumerable GetSubSequences();
        void SetPlaying(bool start);
    }
    class WindowState : IWindowState
    {
        const int k_TimeCodeTextFieldId = 3790;
        readonly TimelineWindow m_Window;
        bool m_Recording;
        readonly SpacePartitioner m_SpacePartitioner = new SpacePartitioner();
        readonly SpacePartitioner m_HeaderSpacePartitioner = new SpacePartitioner();
        readonly List m_CaptureSession = new List();
        int m_DirtyStamp;
        float m_BindingAreaWidth = WindowConstants.defaultBindingAreaWidth;
        bool m_MustRebuildGraph;
        float m_LastTime;
        readonly PropertyCollector m_PropertyCollector = new PropertyCollector();
        static AnimationModeDriver s_PreviewDriver;
        PreviewedBindings m_PreviewedAnimators;
        List m_PreviewedComponents;
        IEnumerable previewedComponents =>
            m_PreviewedComponents.Where(component => component != null).Cast();
        public static double kTimeEpsilon { get { return TimeUtility.kTimeEpsilon; } }
        public static readonly float kMaxShownTime = (float)TimeUtility.k_MaxTimelineDurationInSeconds;
        static readonly ISequenceState k_NullSequenceState = new NullSequenceState();
        // which tracks are armed for record - only one allowed per 'actor'
        Dictionary m_ArmedTracks = new Dictionary();
        TimelineWindow.TimelineWindowPreferences m_Preferences;
        List m_OnStartFrameUpdates;
        List m_OnEndFrameUpdates;
        readonly SequenceHierarchy m_SequenceHierarchy;
        public event Action windowOnGuiStarted;
        public event Action OnPlayStateChange;
        public event System.Action OnDirtyStampChange;
        public event System.Action OnRebuildGraphChange;
        public event System.Action OnTimeChange;
        public event System.Action OnRecordingChange;
        public event System.Action OnBeforeSequenceChange;
        public event System.Action OnAfterSequenceChange;
        public WindowState(TimelineWindow w, SequenceHierarchy hierarchy)
        {
            m_Window = w;
            m_Preferences = w.preferences;
            hierarchy.Init(this);
            m_SequenceHierarchy = hierarchy;
            TimelinePlayable.muteAudioScrubbing = muteAudioScrubbing;
        }
        public static AnimationModeDriver previewDriver
        {
            get
            {
                if (s_PreviewDriver == null)
                {
                    s_PreviewDriver = ScriptableObject.CreateInstance();
                    AnimationPreviewUtilities.s_PreviewDriver = s_PreviewDriver;
                }
                return s_PreviewDriver;
            }
        }
        public EditorWindow editorWindow
        {
            get { return m_Window; }
        }
        public ISequenceState editSequence
        {
            get
            {
                // Using "null" ISequenceState to avoid checking against null all the time.
                // This *should* be removed in a phase 2 of refactoring, where we make sure
                // to pass around the correct state object instead of letting clients dig
                // into the WindowState for whatever they want.
                return m_SequenceHierarchy.editSequence ?? k_NullSequenceState;
            }
        }
        public ISequenceState masterSequence
        {
            get { return m_SequenceHierarchy.masterSequence ?? k_NullSequenceState; }
        }
        public ISequenceState referenceSequence
        {
            get { return timeReferenceMode == TimeReferenceMode.Local ? editSequence : masterSequence; }
        }
        public IEnumerable allSequences
        {
            get { return m_SequenceHierarchy.allSequences; }
        }
        public bool rebuildGraph
        {
            get { return m_MustRebuildGraph; }
            set { SyncNotifyValue(ref m_MustRebuildGraph, value, OnRebuildGraphChange); }
        }
        public float mouseDragLag { get; set; }
        public SpacePartitioner spacePartitioner
        {
            get { return m_SpacePartitioner; }
        }
        public SpacePartitioner headerSpacePartitioner
        {
            get { return m_HeaderSpacePartitioner; }
        }
        public List captured
        {
            get { return m_CaptureSession; }
        }
        public void AddCaptured(Manipulator manipulator)
        {
            if (!m_CaptureSession.Contains(manipulator))
                m_CaptureSession.Add(manipulator);
        }
        public void RemoveCaptured(Manipulator manipulator)
        {
            m_CaptureSession.Remove(manipulator);
        }
        public bool isJogging { get; set; }
        public int viewStateHash { get; private set; }
        public float bindingAreaWidth
        {
            get { return m_BindingAreaWidth; }
            set { m_BindingAreaWidth = value; }
        }
        public float sequencerHeaderWidth
        {
            get { return editSequence.viewModel.sequencerHeaderWidth; }
            set
            {
                editSequence.viewModel.sequencerHeaderWidth = Mathf.Clamp(value, WindowConstants.minHeaderWidth, WindowConstants.maxHeaderWidth);
            }
        }
        public float mainAreaWidth { get; set; }
        public float trackScale
        {
            get { return editSequence.viewModel.trackScale; }
            set
            {
                editSequence.viewModel.trackScale = value;
                m_Window.treeView.CalculateRowRects();
            }
        }
        public int dirtyStamp
        {
            get { return m_DirtyStamp; }
            private set { SyncNotifyValue(ref m_DirtyStamp, value, OnDirtyStampChange); }
        }
        public bool showQuadTree { get; set; }
        public bool canRecord
        {
            get { return AnimationMode.InAnimationMode(previewDriver) || !AnimationMode.InAnimationMode(); }
        }
        public bool recording
        {
            get
            {
                if (!previewMode)
                    m_Recording = false;
                return m_Recording;
            }
            // set can only be used to disable recording
            set
            {
                if (ignorePreview)
                    return;
                // force preview mode on
                if (value)
                    previewMode = true;
                bool newValue = value;
                if (!previewMode)
                    newValue = false;
                if (newValue && m_ArmedTracks.Count == 0)
                {
                    Debug.LogError("Cannot enable recording without an armed track");
                    newValue = false;
                }
                if (!newValue)
                    m_ArmedTracks.Clear();
                if (newValue != m_Recording)
                {
                    if (newValue)
                        AnimationMode.StartAnimationRecording();
                    else
                        AnimationMode.StopAnimationRecording();
                    InspectorWindow.RepaintAllInspectors();
                }
                SyncNotifyValue(ref m_Recording, newValue, OnRecordingChange);
            }
        }
        public bool previewMode
        {
            get { return ignorePreview || AnimationMode.InAnimationMode(previewDriver); }
            set
            {
                if (ignorePreview)
                    return;
                bool inAnimationMode = AnimationMode.InAnimationMode(previewDriver);
                if (!value)
                {
                    if (inAnimationMode)
                    {
                        Stop();
                        OnStopPreview();
                        AnimationMode.StopAnimationMode(previewDriver);
                        AnimationPropertyContextualMenu.Instance.SetResponder(null);
                        previewedDirectors = null;
                    }
                }
                else if (!inAnimationMode)
                {
                    editSequence.time = editSequence.viewModel.windowTime;
                    EvaluateImmediate(); // does appropriate caching prior to enabling
                }
            }
        }
        public bool playing
        {
            get
            {
                return masterSequence.director != null && masterSequence.director.state == PlayState.Playing;
            }
        }
        public float playbackSpeed { get; set; }
        public bool frameSnap
        {
            get { return TimelinePreferences.instance.snapToFrame; }
            set { TimelinePreferences.instance.snapToFrame = value; }
        }
        public bool edgeSnaps
        {
            get { return TimelinePreferences.instance.edgeSnap; }
            set { TimelinePreferences.instance.edgeSnap = value; }
        }
        public bool muteAudioScrubbing
        {
            get { return !TimelinePreferences.instance.audioScrubbing; }
            set
            {
                TimelinePreferences.instance.audioScrubbing = !value;
                TimelinePlayable.muteAudioScrubbing = value;
                RebuildPlayableGraph();
            }
        }
        public TimeReferenceMode timeReferenceMode
        {
            get { return m_Preferences.timeReferenceMode; }
            set { m_Preferences.timeReferenceMode = value; }
        }
        public TimeFormat timeFormat
        {
            get { return TimelinePreferences.instance.timeFormat; }
            set { TimelinePreferences.instance.timeFormat = value; }
        }
        public bool showAudioWaveform
        {
            get { return TimelinePreferences.instance.showAudioWaveform; }
            set { TimelinePreferences.instance.showAudioWaveform = value; }
        }
        public PlayRange playRange
        {
            get { return masterSequence.viewModel.timeAreaPlayRange; }
            set { masterSequence.viewModel.timeAreaPlayRange = ValidatePlayRange(value, masterSequence); }
        }
        public bool showMarkerHeader
        {
            get { return editSequence.asset != null && editSequence.asset.markerTrack != null && editSequence.asset.markerTrack.GetShowMarkers(); }
            set { GetWindow().SetShowMarkerHeader(value); }
        }
        public EditMode.EditType editType
        {
            get { return m_Preferences.editType; }
            set { m_Preferences.editType = value; }
        }
        public PlaybackScrollMode autoScrollMode
        {
            get { return TimelinePreferences.instance.playbackScrollMode; }
            set { TimelinePreferences.instance.playbackScrollMode = value; }
        }
        public List previewedDirectors { get; private set; }
        public void OnDestroy(bool clearHierarchy = true)
        {
            if (!ignorePreview)
                Stop();
            if (m_OnStartFrameUpdates != null)
                m_OnStartFrameUpdates.Clear();
            if (m_OnEndFrameUpdates != null)
                m_OnEndFrameUpdates.Clear();
            if (clearHierarchy)
                m_SequenceHierarchy.Clear();
            windowOnGuiStarted = null;
        }
        public void OnSceneSaved()
        {
            // the director will reset it's time when the scene is saved.
            EnsureWindowTimeConsistency();
        }
        public void SetCurrentSequence(TimelineAsset timelineAsset, PlayableDirector director, TimelineClip hostClip)
        {
            if (OnBeforeSequenceChange != null)
                OnBeforeSequenceChange.Invoke();
            OnCurrentDirectorWillChange();
            if (hostClip == null || timelineAsset == null)
            {
                m_PropertyCollector.Clear();
                m_SequenceHierarchy.Clear();
            }
            if (timelineAsset != null)
                m_SequenceHierarchy.Add(timelineAsset, director, hostClip);
            if (OnAfterSequenceChange != null)
                OnAfterSequenceChange.Invoke();
        }
        public void PopSequencesUntilCount(int count)
        {
            if (count >= m_SequenceHierarchy.count) return;
            if (count < 1) return;
            if (OnBeforeSequenceChange != null)
                OnBeforeSequenceChange.Invoke();
            var nextDirector = m_SequenceHierarchy.GetStateAtIndex(count - 1).director;
            OnCurrentDirectorWillChange();
            m_SequenceHierarchy.RemoveUntilCount(count);
            EnsureWindowTimeConsistency();
            if (OnAfterSequenceChange != null)
                OnAfterSequenceChange.Invoke();
        }
        public SequencePath GetCurrentSequencePath()
        {
            return m_SequenceHierarchy.ToSequencePath();
        }
        public void SetCurrentSequencePath(SequencePath path, bool forceRebuild)
        {
            if (!m_SequenceHierarchy.NeedsUpdate(path, forceRebuild))
                return;
            if (OnBeforeSequenceChange != null)
                OnBeforeSequenceChange.Invoke();
            m_SequenceHierarchy.FromSequencePath(path, forceRebuild);
            if (OnAfterSequenceChange != null)
                OnAfterSequenceChange.Invoke();
        }
        public IEnumerable GetAllSequences()
        {
            return m_SequenceHierarchy.allSequences;
        }
        public IEnumerable GetSubSequences()
        {
            var contexts =
                editSequence.asset?.flattenedTracks
                    .SelectMany(x => x.clips)
                    .Where((TimelineUtility.HasCustomEditor))
                    .SelectMany((clip =>
                        TimelineUtility.GetSubTimelines(clip, TimelineEditor.inspectedDirector)
                            .Select(director => new SequenceContext(director, clip))));
            return contexts;
        }
        public void Reset()
        {
            recording = false;
            previewMode = false;
        }
        public double GetSnappedTimeAtMousePosition(Vector2 mousePos)
        {
            return TimeReferenceUtility.SnapToFrameIfRequired(ScreenSpacePixelToTimeAreaTime(mousePos.x));
        }
        static void SyncNotifyValue(ref T oldValue, T newValue, System.Action changeStateCallback)
        {
            var stateChanged = false;
            if (oldValue == null)
            {
                oldValue = newValue;
                stateChanged = true;
            }
            else
            {
                if (!oldValue.Equals(newValue))
                {
                    oldValue = newValue;
                    stateChanged = true;
                }
            }
            if (stateChanged && changeStateCallback != null)
            {
                changeStateCallback.Invoke();
            }
        }
        public TimelineWindowAnalytics analytics = new TimelineWindowAnalytics();
        public void SetTimeAreaTransform(Vector2 newTranslation, Vector2 newScale)
        {
            m_Window.timeArea.SetTransform(newTranslation, newScale);
            TimeAreaChanged();
        }
        public void SetTimeAreaShownRange(float min, float max)
        {
            m_Window.timeArea.SetShownHRange(min, max);
            TimeAreaChanged();
        }
        internal void TimeAreaChanged()
        {
            if (editSequence.asset != null)
            {
                editSequence.viewModel.timeAreaShownRange = new Vector2(m_Window.timeArea.shownArea.x, m_Window.timeArea.shownArea.xMax);
            }
        }
        public void ResetPreviewMode()
        {
            var mode = previewMode;
            previewMode = false;
            previewMode = mode;
        }
        public bool TimeIsInRange(float value)
        {
            Rect shownArea = m_Window.timeArea.shownArea;
            return value >= shownArea.x && value <= shownArea.xMax;
        }
        public bool RangeIsVisible(Range range)
        {
            var shownArea = m_Window.timeArea.shownArea;
            return range.start < shownArea.xMax && range.end > shownArea.xMin;
        }
        public void EnsurePlayHeadIsVisible()
        {
            double minDisplayedTime = PixelToTime(timeAreaRect.xMin);
            double maxDisplayedTime = PixelToTime(timeAreaRect.xMax);
            double currentTime = editSequence.time;
            if (currentTime >= minDisplayedTime && currentTime <= maxDisplayedTime)
                return;
            float displayedTimeRange = (float)(maxDisplayedTime - minDisplayedTime);
            float minimumTimeToDisplay = (float)currentTime - displayedTimeRange / 2.0f;
            float maximumTimeToDisplay = (float)currentTime + displayedTimeRange / 2.0f;
            SetTimeAreaShownRange(minimumTimeToDisplay, maximumTimeToDisplay);
        }
        public void SetPlayHeadToMiddle()
        {
            double minDisplayedTime = PixelToTime(timeAreaRect.xMin);
            double maxDisplayedTime = PixelToTime(timeAreaRect.xMax);
            double currentTime = editSequence.time;
            float displayedTimeRange = (float)(maxDisplayedTime - minDisplayedTime);
            if (currentTime >= minDisplayedTime && currentTime <= maxDisplayedTime)
            {
                if (currentTime < minDisplayedTime + displayedTimeRange / 2)
                    return;
            }
            const float kCatchUpSpeed = 3f;
            float realDelta = Mathf.Clamp(Time.realtimeSinceStartup - m_LastTime, 0f, 1f) * kCatchUpSpeed;
            float scrollCatchupAmount = kCatchUpSpeed * realDelta * displayedTimeRange / 2;
            if (currentTime < minDisplayedTime)
            {
                SetTimeAreaShownRange((float)currentTime, (float)currentTime + displayedTimeRange);
            }
            else if (currentTime > maxDisplayedTime)
            {
                SetTimeAreaShownRange((float)currentTime - displayedTimeRange + scrollCatchupAmount, (float)currentTime + scrollCatchupAmount);
            }
            else if (currentTime > minDisplayedTime + displayedTimeRange / 2)
            {
                float targetMinDisplayedTime = Mathf.Min((float)minDisplayedTime + scrollCatchupAmount,
                    (float)(currentTime - displayedTimeRange / 2));
                SetTimeAreaShownRange(targetMinDisplayedTime, targetMinDisplayedTime + displayedTimeRange);
            }
        }
        internal void UpdateLastFrameTime()
        {
            m_LastTime = Time.realtimeSinceStartup;
        }
        public Vector2 timeAreaShownRange
        {
            get
            {
                if (m_Window.state.editSequence.asset != null)
                    return editSequence.viewModel.timeAreaShownRange;
                return TimelineAssetViewModel.TimeAreaDefaultRange;
            }
            set
            {
                SetTimeAreaShownRange(value.x, value.y);
            }
        }
        public Vector2 timeAreaTranslation
        {
            get { return m_Window.timeArea.translation; }
        }
        public Vector2 timeAreaScale
        {
            get { return m_Window.timeArea.scale; }
        }
        public Rect timeAreaRect
        {
            get
            {
                var sequenceContentRect = m_Window.sequenceContentRect;
                return new Rect(
                    sequenceContentRect.x,
                    WindowConstants.timeAreaYPosition,
                    Mathf.Max(sequenceContentRect.width, WindowConstants.timeAreaMinWidth),
                    WindowConstants.timeAreaHeight
                );
            }
        }
        public float windowHeight
        {
            get { return m_Window.position.height; }
        }
        public bool playRangeEnabled
        {
            get { return !ignorePreview && masterSequence.viewModel.playRangeEnabled && !IsEditingASubTimeline(); }
            set
            {
                if (!ignorePreview)
                    masterSequence.viewModel.playRangeEnabled = value;
            }
        }
        public bool ignorePreview
        {
            get
            {
                var shouldIgnorePreview = masterSequence.asset != null && !masterSequence.asset.editorSettings.scenePreview;
                return Application.isPlaying || shouldIgnorePreview;
            }
        }
        public TimelineWindow GetWindow()
        {
            return m_Window;
        }
        public void Play()
        {
            if (masterSequence.director == null)
                return;
            if (!previewMode)
                previewMode = true;
            if (previewMode)
            {
                if (masterSequence.time > masterSequence.duration)
                    masterSequence.time = 0;
#if TIMELINE_FRAMEACCURATE
                if (TimelinePreferences.instance.playbackLockedToFrame)
                {
                    FrameRate frameRate = FrameRate.DoubleToFrameRate(masterSequence.asset.editorSettings.frameRate);
                    masterSequence.director.Play(frameRate);
                }
                else
                {
                    masterSequence.director.Play();
                }
#else
                masterSequence.director.Play();
#endif
                masterSequence.director.ProcessPendingGraphChanges();
                PlayableDirector.ResetFrameTiming();
                InvokePlayStateChangeCallback(true);
            }
        }
        public void Pause()
        {
            if (masterSequence.director != null)
            {
                masterSequence.director.Pause();
                masterSequence.director.ProcessPendingGraphChanges();
                SynchronizeSequencesAfterPlayback();
                InvokePlayStateChangeCallback(false);
            }
        }
        public void SetPlaying(bool start)
        {
            if (start && !playing)
            {
                Play();
            }
            if (!start && playing)
            {
                Pause();
            }
            analytics.SendPlayEvent(start);
        }
        public void Stop()
        {
            if (masterSequence.director != null)
            {
                masterSequence.director.Stop();
                masterSequence.director.ProcessPendingGraphChanges();
                InvokePlayStateChangeCallback(false);
            }
        }
        void InvokePlayStateChangeCallback(bool isPlaying)
        {
            if (OnPlayStateChange != null)
                OnPlayStateChange.Invoke(isPlaying);
        }
        public void RebuildPlayableGraph()
        {
            if (masterSequence.director != null)
            {
                masterSequence.director.RebuildGraph();
                // rebuild both the parent and the edit sequences. control tracks don't necessary
                // rebuild the subdirector on recreation
                if (editSequence.director != null && editSequence.director != masterSequence.director)
                {
                    editSequence.director.RebuildGraph();
                }
            }
        }
        public void Evaluate()
        {
            if (masterSequence.director != null)
            {
                if (!EditorApplication.isPlaying && !previewMode)
                    GatherProperties(masterSequence.director);
                ForceTimeOnDirector(masterSequence.director);
                masterSequence.director.DeferredEvaluate();
                if (EditorApplication.isPlaying == false)
                {
                    PlayModeView.RepaintAll();
                    SceneView.RepaintAll();
                    AudioMixerWindow.RepaintAudioMixerWindow();
                }
            }
        }
        public void EvaluateImmediate()
        {
            if (masterSequence.director != null && masterSequence.director.isActiveAndEnabled)
            {
                if (!EditorApplication.isPlaying && !previewMode)
                    GatherProperties(masterSequence.director);
                if (previewMode)
                {
                    ForceTimeOnDirector(masterSequence.director);
                    masterSequence.director.ProcessPendingGraphChanges();
                    masterSequence.director.Evaluate();
                }
            }
        }
        public void Refresh()
        {
            CheckRecordingState();
            dirtyStamp = dirtyStamp + 1;
            rebuildGraph = true;
        }
        public void UpdateViewStateHash()
        {
            viewStateHash = timeAreaTranslation.GetHashCode()
                .CombineHash(timeAreaScale.GetHashCode())
                .CombineHash(trackScale.GetHashCode());
        }
        public bool IsEditingASubTimeline()
        {
            return editSequence != masterSequence;
        }
        public bool IsEditingAnEmptyTimeline()
        {
            return editSequence.asset == null;
        }
        public bool IsEditingAPrefabAsset()
        {
            var stage = PrefabStageUtility.GetCurrentPrefabStage();
            return stage != null && editSequence.director != null && stage.IsPartOfPrefabContents(editSequence.director.gameObject);
        }
        public bool IsCurrentEditingASequencerTextField()
        {
            if (editSequence.asset == null)
                return false;
            if (k_TimeCodeTextFieldId == GUIUtility.keyboardControl)
                return true;
            return editSequence.asset.flattenedTracks.Count(t => t.GetInstanceID() == GUIUtility.keyboardControl) != 0;
        }
        public float TimeToTimeAreaPixel(double t) // TimeToTimeAreaPixel
        {
            float pixelX = (float)t;
            pixelX *= timeAreaScale.x;
            pixelX += timeAreaTranslation.x + sequencerHeaderWidth;
            return pixelX;
        }
        public float TimeToScreenSpacePixel(double time)
        {
            float pixelX = (float)time;
            pixelX *= timeAreaScale.x;
            pixelX += timeAreaTranslation.x;
            return pixelX;
        }
        public float TimeToPixel(double time)
        {
            return m_Window.timeArea.TimeToPixel((float)time, timeAreaRect);
        }
        public float PixelToTime(float pixel)
        {
            return m_Window.timeArea.PixelToTime(pixel, timeAreaRect);
        }
        public float PixelDeltaToDeltaTime(float p)
        {
            return PixelToTime(p) - PixelToTime(0);
        }
        public float TimeAreaPixelToTime(float pixel)
        {
            return PixelToTime(pixel);
        }
        public float ScreenSpacePixelToTimeAreaTime(float p)
        {
            // transform into track space by offsetting the pixel by the screen-space offset of the time area
            p -= timeAreaRect.x;
            return TrackSpacePixelToTimeAreaTime(p);
        }
        public float TrackSpacePixelToTimeAreaTime(float p)
        {
            p -= timeAreaTranslation.x;
            if (timeAreaScale.x > 0.0f)
                return p / timeAreaScale.x;
            return p;
        }
        public void OffsetTimeArea(int pixels)
        {
            Vector3 tx = timeAreaTranslation;
            tx.x += pixels;
            SetTimeAreaTransform(tx, timeAreaScale);
        }
        public GameObject GetSceneReference(TrackAsset asset)
        {
            if (editSequence.director == null)
                return null; // no player bound
            return TimelineUtility.GetSceneGameObject(editSequence.director, asset);
        }
        public void CalculateRowRects()
        {
            // arming a track might add inline curve tracks, recalc track heights
            if (m_Window != null && m_Window.treeView != null)
                m_Window.treeView.CalculateRowRects();
        }
        // Only one track within a 'track' hierarchy can be armed
        public void ArmForRecord(TrackAsset track)
        {
            m_ArmedTracks[TimelineUtility.GetSceneReferenceTrack(track)] = track;
            if (track != null && !recording)
                recording = true;
            if (!recording)
                return;
            track.OnRecordingArmed(editSequence.director);
            CalculateRowRects();
        }
        public void UnarmForRecord(TrackAsset track)
        {
            m_ArmedTracks.Remove(TimelineUtility.GetSceneReferenceTrack(track));
            if (m_ArmedTracks.Count == 0)
                recording = false;
            track.OnRecordingUnarmed(editSequence.director);
        }
        public void UpdateRecordingState()
        {
            if (recording)
            {
                foreach (var track in m_ArmedTracks.Values)
                {
                    if (track != null)
                        track.OnRecordingTimeChanged(editSequence.director);
                }
            }
        }
        public bool IsTrackRecordable(TrackAsset track)
        {
            // A track with animated parameters can always be recorded to
            return IsArmedForRecord(track) || track.HasAnyAnimatableParameters();
        }
        public bool IsArmedForRecord(TrackAsset track)
        {
            return track == GetArmedTrack(track);
        }
        public TrackAsset GetArmedTrack(TrackAsset track)
        {
            TrackAsset outTrack;
            m_ArmedTracks.TryGetValue(TimelineUtility.GetSceneReferenceTrack(track), out outTrack);
            return outTrack;
        }
        void CheckRecordingState()
        {
            // checks for deleted tracks, and makes sure the recording state matches
            if (m_ArmedTracks.Any(t => t.Value == null))
            {
                m_ArmedTracks = m_ArmedTracks.Where(t => t.Value != null).ToDictionary(t => t.Key, t => t.Value);
                if (m_ArmedTracks.Count == 0)
                    recording = false;
            }
        }
        void OnCurrentDirectorWillChange()
        {
            if (ignorePreview)
                return;
            SynchronizeViewModelTime(editSequence);
            Stop();
            rebuildGraph = true; // needed for asset previews
        }
        public void GatherProperties(PlayableDirector director)
        {
            if (director == null || Application.isPlaying)
                return;
            var asset = director.playableAsset as TimelineAsset;
            if (asset != null && !asset.editorSettings.scenePreview)
                return;
            if (!previewMode)
            {
                AnimationMode.StartAnimationMode(previewDriver);
                OnStartPreview(director);
                AnimationPropertyContextualMenu.Instance.SetResponder(new TimelineRecordingContextualResponder(this));
                if (!previewMode)
                    return;
                EnsureWindowTimeConsistency();
            }
            if (asset != null)
            {
                m_PropertyCollector.Reset();
                m_PropertyCollector.PushActiveGameObject(null); // avoid overflow on unbound tracks
                asset.GatherProperties(director, m_PropertyCollector);
            }
        }
        void OnStartPreview(PlayableDirector director)
        {
            previewedDirectors = TimelineUtility.GetAllDirectorsInHierarchy(director).ToList();
            if (previewedDirectors == null)
                return;
            m_PreviewedAnimators = PreviewedBindings.GetPreviewedBindings(previewedDirectors);
            m_PreviewedComponents = m_PreviewedAnimators.GetUniqueBindings()
                .SelectMany(animator => animator.GetComponents()
                    .Cast())
                .ToList();
            foreach (var previewedComponent in previewedComponents)
            {
                previewedComponent.StartPreview();
            }
#if UNITY_2022_2_OR_NEWER
            PrefabUtility.allowRecordingPrefabPropertyOverridesFor += AllowRecordingPrefabPropertyOverridesFor;
#endif //UNITY_2022_2_OR_NEWER
        }
        internal bool AllowRecordingPrefabPropertyOverridesFor(Object componentOrGameObject)
        {
            if (componentOrGameObject == null)
                throw new ArgumentNullException(nameof(componentOrGameObject));
            if (previewMode == false)
                return true;
            if (m_ArmedTracks.Count == 0)
                return true;
            GameObject inputGameObject = null;
            if (componentOrGameObject is Component component)
            {
                inputGameObject = component.gameObject;
            }
            else if (componentOrGameObject is GameObject gameObject)
            {
                inputGameObject = gameObject;
            }
            if (inputGameObject == null)
                return true;
            var armedTracks = m_ArmedTracks.Keys;
            foreach (var track in armedTracks)
            {
                var animators = m_PreviewedAnimators.GetBindingsForObject(track);
                foreach (var animator in animators)
                {
                    if (animator == null)
                        continue;
                    if (inputGameObject.transform.IsChildOf(animator.transform))
                        return false;
                }
            }
            return true;
        }
        void OnStopPreview()
        {
            if (m_PreviewedComponents != null)
            {
                foreach (var previewComponent in previewedComponents)
                {
                    previewComponent.StopPreview();
                }
                m_PreviewedComponents = null;
            }
            foreach (var previewAnimator in m_PreviewedAnimators.GetUniqueBindings())
            {
                if (previewAnimator != null)
                    previewAnimator.UnbindAllHandles();
            }
            m_PreviewedAnimators = default;
#if UNITY_2022_2_OR_NEWER
            PrefabUtility.allowRecordingPrefabPropertyOverridesFor -= AllowRecordingPrefabPropertyOverridesFor;
#endif //UNITY_2022_2_OR_NEWER
        }
        internal void ProcessStartFramePendingUpdates()
        {
            if (m_OnStartFrameUpdates != null)
                m_OnStartFrameUpdates.RemoveAll(callback => callback.Invoke(this, Event.current));
        }
        internal void ProcessEndFramePendingUpdates()
        {
            if (m_OnEndFrameUpdates != null)
                m_OnEndFrameUpdates.RemoveAll(callback => callback.Invoke(this, Event.current));
        }
        public void AddStartFrameDelegate(PendingUpdateDelegate updateDelegate)
        {
            if (m_OnStartFrameUpdates == null)
                m_OnStartFrameUpdates = new List();
            if (m_OnStartFrameUpdates.Contains(updateDelegate))
                return;
            m_OnStartFrameUpdates.Add(updateDelegate);
        }
        public void AddEndFrameDelegate(PendingUpdateDelegate updateDelegate)
        {
            if (m_OnEndFrameUpdates == null)
                m_OnEndFrameUpdates = new List();
            if (m_OnEndFrameUpdates.Contains(updateDelegate))
                return;
            m_OnEndFrameUpdates.Add(updateDelegate);
        }
        internal void InvokeWindowOnGuiStarted(Event evt)
        {
            if (windowOnGuiStarted != null)
                windowOnGuiStarted.Invoke(this, evt);
        }
        public void UpdateRootPlayableDuration(double duration)
        {
            if (editSequence.director != null)
            {
                if (editSequence.director.playableGraph.IsValid())
                {
                    if (editSequence.director.playableGraph.GetRootPlayableCount() > 0)
                    {
                        var rootPlayable = editSequence.director.playableGraph.GetRootPlayable(0);
                        if (rootPlayable.IsValid())
                            rootPlayable.SetDuration(duration);
                    }
                }
            }
        }
        public void InvokeTimeChangeCallback()
        {
            if (OnTimeChange != null)
                OnTimeChange.Invoke();
        }
        PlayRange ValidatePlayRange(PlayRange range, ISequenceState sequenceState)
        {
            if (range == TimelineAssetViewModel.NoPlayRangeSet)
                return range;
            double minimumPlayRangeTime = (0.01 / Math.Max(1.0, sequenceState.frameRate));
            // Validate min
            if (range.end - range.start < minimumPlayRangeTime)
                range.start = range.end - minimumPlayRangeTime;
            if (range.start < 0.0)
                range.start = 0.0;
            // Validate max
            if (range.end > sequenceState.duration)
                range.end = sequenceState.duration;
            if (range.end - range.start < minimumPlayRangeTime)
                range.end = Math.Min(range.start + minimumPlayRangeTime, sequenceState.duration);
            return range;
        }
        void EnsureWindowTimeConsistency()
        {
            if (masterSequence.director != null && masterSequence.viewModel != null && !ignorePreview)
                masterSequence.time = masterSequence.viewModel.windowTime;
        }
        void SynchronizeSequencesAfterPlayback()
        {
            // Synchronizing editSequence will synchronize all view models up to the master
            SynchronizeViewModelTime(editSequence);
        }
        static void SynchronizeViewModelTime(ISequenceState state)
        {
            if (state.director == null || state.viewModel == null)
                return;
            var t = state.time;
            state.time = t;
        }
        // because we may be evaluating outside the duration of the root playable
        //  we explicitly set the time - this causes the graph to not 'advance' the time
        //  because advancing it can force it to change due to wrapping to the duration
        // This can happen if the graph is force evaluated outside it's duration
        // case 910114, 936844 and 943377
        static void ForceTimeOnDirector(PlayableDirector director)
        {
            var directorTime = director.time;
            director.time = directorTime;
        }
        public bool IsPlayableGraphDone()
        {
            return masterSequence.director != null
                && masterSequence.director.playableGraph.IsValid()
                && masterSequence.director.playableGraph.IsDone();
        }
    }
}