# SPDX-License-Identifier: GPL-2.0-or-later


"""
Pose Library - operators.
"""

from math import radians
from mathutils import Vector
from pathlib import Path
from tempfile import gettempdir
from typing import Optional, Set

import bpy
import json
import os
import re
import shutil
import subprocess
import uuid
import time
from pathlib import Path
from functools import partial
from pprint import pprint


from asset_library.pose.pose_creation import(
    create_pose_asset_from_context,
    assign_from_asset_browser
)

from asset_library.pose.pose_usage import(
    select_bones,
    flip_side_name
)

from asset_library.action.functions import(
    apply_anim,
    append_action,
    clean_action,
    reset_bone,
    is_asset_action,
    conform_action
)

from bpy.props import (
    BoolProperty,
    CollectionProperty,
    EnumProperty,
    PointerProperty,
    StringProperty,
    IntProperty
)

from bpy.types import (
    Action,
    Context,
    Event,
    FileSelectEntry,
    Object,
    Operator,
    PropertyGroup,
)

from bpy_extras import asset_utils
from bpy_extras.io_utils import ExportHelper, ImportHelper

from asset_library.action.functions import (
    is_pose,
    get_marker,
    get_keyframes,
)

from asset_library.common.functions import (
    #get_actionlib_dir,
    #get_asset_source,
    #get_catalog_path,
    #read_catalog,
    #set_actionlib_dir,
    #resync_lib,
    get_active_library,
    get_active_catalog,
    asset_warning_callback
)

from asset_library.common.bl_utils import (
    area_from_context,
    attr_set,
    get_addon_prefs,
    copy_frames,
    split_path,
    get_preview,
    get_view3d_persp,
    get_viewport,
    #load_assets_from,
    get_asset_space_params,
    get_bl_cmd,
    get_overriden_col
)

from asset_library.common.file_utils import (
    remove_version,
    synchronize,
    open_file,
    copy_dir,
)


class ACTIONLIB_OT_restore_previous_action(Operator):
    bl_idname = "actionlib.restore_previous_action"
    bl_label = "Restore Previous Action"
    bl_description = "Switch back to the previous Action, after creating a pose asset"
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context: Context) -> bool:
        return bool(
            context.scene.actionlib.previous_action
            and context.object
            and context.object.animation_data
            and context.object.animation_data.action
            and context.object.animation_data.action.asset_data is not None
        )

    def execute(self, context: Context) -> Set[str]:
        # This is the Action that was just created with "Create Pose Asset".
        # It has to be re-applied after switching to the previous action,
        # to ensure the character keeps the same pose.
        self.pose_action = context.object.animation_data.action

        prev_action = context.scene.actionlib.previous_action
        context.object.animation_data.action = prev_action
        context.scene.actionlib.previous_action = None

        # Wait a bit for the action assignment to be handled, before applying the pose.
        wm = context.window_manager
        self._timer = wm.event_timer_add(0.001, window=context.window)
        wm.modal_handler_add(self)

        return {'RUNNING_MODAL'}

    def modal(self, context, event):
        if event.type != 'TIMER':
            return {'RUNNING_MODAL'}

        wm = context.window_manager
        wm.event_timer_remove(self._timer)

        context.object.pose.apply_pose_from_action(self.pose_action)
        return {'FINISHED'}


class ACTIONLIB_OT_assign_action(Operator):
    bl_idname = "actionlib.assign_action"
    bl_label = "Assign Action"
    bl_description = "Set this pose Action as active Action on the active Object"
    bl_options = {"REGISTER", "UNDO"}

    assign: BoolProperty(name="Assign", default=True)  # type: ignore

    def execute(self, context: Context) -> Set[str]:
        if self.assign:
            context.object.animation_data_create().action = context.id
        else:
            if context.object.animation_data.action:
                context.object.animation_data.action = None

        return {"FINISHED"}


class ACTIONLIB_OT_replace_pose(Operator):
    bl_idname = "actionlib.replace_pose"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Update Pose'
    bl_description = 'Update selected Pose. ! Works only on Pose, not Anim !'
    
    @classmethod
    def poll(cls, context: Context) -> bool:
        # wm = context.window_manager
        # if context.active_file and wm.edit_pose:
        #     if context.active_file.name == wm.edit_pose_action:
        #         return  True
        #     else:
        #         cls.poll_message_set(f"Current Action {context.id.name} different than Edit Action {wm.edit_pose_action}")
        #         return False
        if context.mode == 'POSE' and context.area.ui_type == 'ASSETS' and context.active_file:
            return True

    def execute(self, context: Context) -> Set[str]:
        wm = context.window_manager
        active = context.active_file
        #print('active: ', active)

        select_bones(
            context.object, 
            context.asset_file_handle.local_id, 
            selected_side='BOTH',
            toggle=False)

        data = {
            'name':active.name,
            'catalog_id':active.asset_data.catalog_id,
        }
        data.update(dict(active.asset_data))

        # if 'camera' in asset_data.keys():
        #     data.update({'camera':active.asset_data['camera']})
        # if 'is_single_frame' in asset_data.keys():
        #     data.update({'is_single_frame' : active.asset_data['is_single_frame']})
        
        #print('data: ', data)

        action = create_pose_asset_from_context(
            context,
            data['name'],
        )
        if not action:
            self.report(  # type: ignore
                {"ERROR"},
                f"Can't Update Current Pose",
            )
            return {"CANCELLED"}

        bpy.ops.asset.clear()
        _old_action = bpy.data.actions[active.name]
        _old_action.use_fake_user = False
        bpy.data.actions.remove(_old_action)

        action.name = data['name']
        action.asset_data.catalog_id = data['catalog_id']

        for k, v in data.items():
            if k in ('camera', 'is_single_frame'):
                action.asset_data[k] = v
        
        if not is_pose(action) and 'pose' in action.asset_data.tags.keys() and 'anim' not in action.asset_data.tags.keys():
            for tag in action.asset_data.tags:
                if tag != 'pose':
                    continue
                action.asset_data.tags.remove(tag)
            action.asset_data.tags.new('anim')

        
        return {'FINISHED'}


class ACTIONLIB_OT_apply_anim(Operator):
    bl_idname = "actionlib.apply_anim"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Apply Anim'
    bl_description = 'Apply selected Anim to selected bones'

    @classmethod
    def poll(cls, context: Context) -> bool:
        return context.mode == 'POSE'
    
    def execute(self, context: Context) -> Set[str]:
        ob = context.object
        active_action = context.active_file
        to_remove = False

        prefs = get_addon_prefs()
        #params = get_asset_space_params(context.area)
        asset_library_ref = context.asset_library_ref

        if asset_library_ref == 'LOCAL':
            action = bpy.data.actions[active_action.name]
            to_remove = False
        else:
            asset_file_handle = bpy.context.asset_file_handle
            if asset_file_handle is None:
                return {'CANCELLED'}

            if asset_file_handle.local_id:
                return {'CANCELLED'}

            lib = get_active_library()
            if 'filepath' in asset_file_handle.asset_data:
                action_path = asset_file_handle.asset_data['filepath']
                action_path = lib.library_type.format_path(action_path)
            else:
                action_path = bpy.types.AssetHandle.get_full_library_path(
                    asset_file_handle, asset_library_ref
                )

            if action_path and Path(action_path).exists():
                action = append_action(
                    action_path=action_path,
                    action_name=active_action.name,
                )
                to_remove = True
            else:
                self.report({"WARNING"}, f"Could not load action path {action_path} not exist")
                return {"CANCELLED"}

        bones = [b.name for b in context.selected_pose_bones_from_active_object]
        apply_anim(action, context.active_object, bones=bones)
        if to_remove:
            bpy.data.actions.remove(action)

        return {'FINISHED'}


"""
class ACTIONLIB_OT_publish(Operator):
    bl_idname = "actionlib.publish"
    bl_label = "Publish Library"
    bl_description = "Publish Pose Lib"

    #ExportHelper mixin class uses this
    filename_ext = ""
    use_filter_folder = True
    check_extension = None
    # filter_glob = StringProperty(
    #     default="*.blend",
    # )

    filepath: StringProperty(
        name="File Path",
        description="Filepath used for exporting the file",
        maxlen=1024,
        subtype='DIR_PATH',
    )
    selected_actions : CollectionProperty(type=PropertyGroup)
    render_preview : BoolProperty(default=False)
    clean : BoolProperty(default=False)

    def invoke(self, context, _event):
        scn = context.scene

        filename = remove_version(bpy.data.filepath)
        filename = re.sub('_actionlib', '', filename)

        if scn.actionlib.publish_path:
            action_dir = Path(os.path.expandvars(scn.actionlib.publish_path))
            if action_dir.is_file():
                action_dir = action_dir.parent
            self.filepath = str(action_dir)
        else:
            self.filepath = str(Path(get_actionlib_dir()) / Path(filename).stem)

        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}

    def execute(self, context: Context) -> Set[str]:
        scn = context.scene
        scn.actionlib.publish_path = self.filepath.replace(
            get_actionlib_dir(), '$ACTIONLIB_DIR'
        )

        action_dir = Path(self.filepath)

        try:
            bpy.ops.asset.catalogs_save()
        except RuntimeError:
            pass

        actionlib_dir = get_actionlib_dir()

        current_catalog = get_catalog_path()
        current_catalog_data = read_catalog(current_catalog)

        catalog_global = get_catalog_path(actionlib_dir)
        catalog_data = read_catalog(catalog_global)
        
        for d in catalog_global.parent.rglob('*'):
            if not d.is_dir():
                continue

            reldir = d.relative_to(actionlib_dir)

            if reldir not in catalog_data:
                catalog_data[reldir.as_posix()] = {
                    'id':str(uuid.uuid4()),
                    'name':'-'.join(reldir.parts)
                }

        catalog_path = action_dir.relative_to(actionlib_dir)
        for k, v in current_catalog_data.items():
            v['name'] = '-'.join([*catalog_path.parts, v['name']])
            catalog_data[(catalog_path / k).as_posix()] = v

        catalog_global_lines = ['VERSION 1', '']
        for k, v in sorted(catalog_data.items()):
            if any(i in k for i in ('anim', 'pose', 'preview')):
                continue
            catalog_global_lines.append(':'.join([v['id'], k, v['name']]))

        catalog_global.write_text('\n'.join(catalog_global_lines))

        if not self.selected_actions:
            render_actions_name = [a.name for a in bpy.data.actions if is_asset_action(a)]
        else:
            render_actions_name = [a.name for a in self.selected_actions]

        publish_actions_name = []
        publish_actions = {}
        for a in bpy.data.actions:
            if is_asset_action(a):
                if not a.asset_data.catalog_id in publish_actions:
                    publish_actions[a.asset_data.catalog_id] = set()
                
                publish_actions[a.asset_data.catalog_id].add(a)
                publish_actions_name.append(a.name)
        
        for cat_id, actions in publish_actions.items():
            # Cleanup actions
            store_actions = actions.copy()
            for a in actions:
                conform_action(a)
        
            action_rel_path = next(k for k, v in current_catalog_data.items() if v['id']==cat_id)
            action_path = action_dir / action_rel_path
            action_path = action_path.parent / f'{action_dir.name}_{action_path.name}.blend'

            action_path.parent.mkdir(parents=True, exist_ok=True)

            print(f'Saving File to: {action_path}')
            bpy.data.libraries.write(str(action_path), store_actions, compress=True)
            
        # Render Previews
        _tmp_filepath = Path(gettempdir()) / Path(bpy.data.filepath).name
        print('_tmp_filepath: ', _tmp_filepath)

        bpy.ops.wm.save_as_mainfile(filepath=str(_tmp_filepath), copy=True, compress=True)

        if self.render_preview:
            print('[>-] Rendering Preview..')
            cmd = [
                bpy.app.binary_path,
                '--no-window-focus',
                '--window-geometry', '5000', '0', '10', '10', 
                str(_tmp_filepath),
                '--python-use-system-env',
                '--python', str(Path(__file__).parent / 'render_preview.py'),
                '--',
                '--directory', str(action_dir),
                '--asset-catalog', str(current_catalog),
                '--render-actions', *render_actions_name,
                '--publish-actions', *publish_actions_name,
                '--remove-folder', json.dumps(self.clean)
            ]

            print('cmd: ', cmd)
            subprocess.Popen(cmd)

        print('[>-] Publish Done.')
        return {"FINISHED"}
"""


class ACTIONLIB_OT_create_anim_asset(Operator):
    bl_idname = "actionlib.create_anim_asset"
    bl_label = "Create Anim Asset"
    bl_description = (
        "Create a new Action that contains the anim of the selected bones, and mark it as Asset. "
        "The asset will be stored in the current blend file"
    )
    bl_options = {"REGISTER", "UNDO"}

    @classmethod
    def poll(cls, context: Context) -> bool:
        if not context.object:
            return
        if not context.object.animation_data:
            cls.poll_message_set("Object has no Keyframes")
            return
        if not context.object.animation_data.action:
            cls.poll_message_set("Object has no Action")
            return

        # Make sure that if there is an asset browser open, the artist can see the newly created pose asset.
        asset_browse_area: Optional[bpy.types.Area] = area_from_context(context)
        if not asset_browse_area:
            # No asset browser is visible, so there also aren't any expectations
            # that this asset will be visible.
            return True
        
        params = get_asset_space_params(asset_browse_area)
        if params.asset_library_ref != 'LOCAL':
            cls.poll_message_set("Asset Browser must be set to the Current File library")
            return False
        
        return True

    def execute(self, context: Context) -> Set[str]:
        ### ADD ADM
        action = context.object.animation_data.action
        action.asset_mark()
        action.asset_generate_preview()

        data = action.asset_data
        #data.catalog_id = str(uuid.UUID(int=0))
        asset_browse_area: Optional[bpy.types.Area] = area_from_context(context)
        asset_space_params = params(asset_browse_area)
        
        data.catalog_id = asset_space_params.catalog_id

        data_dict = dict(
            is_single_frame=False,
            camera= context.scene.camera.name if context.scene.camera else '',
        )
        data.tags.new('anim')

        for k, v in data_dict.items():
            data[k] = v
        ###

        return {'FINISHED'}


class ACTIONLIB_OT_apply_selected_action(Operator):
    bl_idname = "actionlib.apply_selected_action"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Apply Pose/Anim'
    bl_description = 'Apply selected Action to selected bones'

    flipped: BoolProperty(name="Flipped", default=False)  # type: ignore

    @classmethod
    def poll(cls, context: Context) -> bool:
        if context.mode == 'POSE' and context.area.ui_type == 'ASSETS':
            return True

    def execute(self, context: Context) -> Set[str]:
        active_action = context.active_file
        
        if 'pose' in active_action.asset_data.tags.keys():
            bpy.ops.poselib.apply_pose_asset_for_keymap(flipped=self.flipped)
        else:
            bpy.ops.actionlib.apply_anim()
        
        return {'FINISHED'}


class ACTIONLIB_OT_edit_action(Operator):
    bl_idname = "actionlib.edit_action"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Edit Action'
    bl_description = 'Assign active action and set Camera'

    @classmethod
    def poll(cls, context: Context) -> bool:
        if context.mode == 'POSE' and context.area.ui_type == 'ASSETS' and context.active_file:
            return True

    def execute(self, context: Context) -> Set[str]:
        scn = context.scene
        rest_pose = bpy.data.actions.get(context.id.asset_data.get('rest_pose', ''))

        context.object.animation_data_create()
        keyframes = get_keyframes(context.id)
        if not keyframes:
            self.report({'ERROR'}, f'No Keyframes found for {context.id.name}.')
            return
        scn.frame_set(keyframes[0])

        context.object.animation_data.action = None
        
        for b in context.object.pose.bones:
            if re.match('^[A-Z]+\.', b.name):
                continue
            reset_bone(b)

        context.object.animation_data.action = rest_pose
        context.view_layer.update()
        context.object.animation_data.action = context.id

        if 'camera' in context.id.asset_data.keys():
            scn.camera = bpy.data.objects[context.id.asset_data['camera']]

        return {"FINISHED"}


class ACTIONLIB_OT_clear_action(Operator):
    bl_idname = "actionlib.clear_action"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Clear Action'
    bl_description = 'Assign active action and set Camera'

    @classmethod
    def poll(cls, context: Context) -> bool:
        if context.mode == 'POSE' and context.area.ui_type == 'ASSETS' and context.active_file:
            return True

    def execute(self, context: Context) -> Set[str]:
        wm = context.window_manager

        context.object.animation_data_create()
        context.object.animation_data.action = None
        context.id.asset_generate_preview()

        for a in context.screen.areas:
            if a.type == 'DOPESHEET_EDITOR' and a.ui_type == 'DOPESHEET':
                a.tag_redraw()

        return {"FINISHED"}


class ACTIONLIB_OT_generate_preview(Operator):
    bl_idname = "actionlib.generate_preview"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Generate Preview'
    bl_description = 'Genreate Preview for Active Action'

    @classmethod
    def poll(cls, context: Context) -> bool:
        if context.object.type == 'ARMATURE' and context.mode == 'POSE':
            return True

    def execute(self, context: Context) -> Set[str]:
        _action = context.object.animation_data.action
        _camera = context.scene.camera

        context.object.animation_data_create()
        actions = context.selected_files + [_action]

        for a in context.selected_files:
            rest_pose = bpy.data.actions.get(a.asset_data.get('rest_pose', ''))
            if rest_pose:
                context.object.animation_data.action = rest_pose
                bpy.context.view_layer.update()
            else:
                context.object.animation_data.action = None
                for b in context.object.pose.bones:
                    if re.match('^[A-Z]+\.', b.name):
                        continue
                    reset_bone(b)

            a_cam = bpy.data.objects.get(a.asset_data['camera'])
            if a_cam:
                context.scene.camera = a_cam
                bpy.context.view_layer.update()

            a.local_id.asset_generate_preview()
        
        context.object.animation_data.action = _action
        context.scene.camera = _camera
        bpy.context.view_layer.update()

        return {"FINISHED"}


class ACTIONLIB_OT_update_action_data(Operator):
    bl_idname = "actionlib.update_action_data"
    bl_options = {"REGISTER", "INTERNAL"}
    bl_label = 'Udpate Action Data'
    bl_description = 'Update Action Metadata'

    tags: EnumProperty(
        name='Tags',
        items=(
            ('POSE', "pose", ""),
            ('ANIM', "anim", ""),
        )
    )

    use_tags: BoolProperty(default=False)
    use_camera: BoolProperty(default=False)
    use_rest_pose: BoolProperty(default=False)

    @classmethod
    def poll(cls, context: Context) -> bool:
        if context.id:
            return True

    def invoke(self, context, event):
        scn = context.scene
        scn.actionlib.camera = scn.camera

        return context.window_manager.invoke_props_dialog(self)
    
    def draw(self, context):
        layout = self.layout
        layout.use_property_split = True

        scn = context.scene

        heading = layout.column(align=True, heading="Tags")
        row = heading.row(align=True)
        row.prop(self, "use_tags", text="")
        sub = row.row()
        sub.active = self.use_tags
        sub.prop(self, "tags", text="")
        
        heading = layout.column(align=True, heading="Camera")
        row = heading.row(align=True)
        row.prop(self, "use_camera", text="")
        sub = row.row()
        sub.active = self.use_camera
        sub.prop(scn.actionlib, "camera", text="", icon='CAMERA_DATA')

        heading = layout.column(align=True, heading="Rest Pose")
        row = heading.row(align=True)
        row.prop(self, "use_rest_pose", text="")
        sub = row.row()
        sub.active = self.use_rest_pose
        sub.prop(scn.actionlib, "rest_pose", text="")

    def execute(self, context):
        scn = context.scene

        for action in context.selected_files:
            if self.use_camera:
                action.asset_data['camera'] = context.scene.actionlib.camera.name

            if self.use_tags:
                tag = self.tags.lower()
                not_tag = 'anim' if tag == 'pose' else 'pose'

                if tag not in action.asset_data.tags.keys():
                    if not_tag in action.asset_data.tags.keys():
                        for t in action.asset_data.tags:
                            if t != not_tag:
                                continue
                            action.asset_data.tags.remove(t)

                    action.asset_data.tags.new(tag)

            if 'pose' in action.asset_data.tags.keys():
                action.asset_data['is_single_frame'] = True
            else:
                action.asset_data['is_single_frame'] = False


            if self.use_rest_pose:
                name = scn.actionlib.rest_pose.name if scn.actionlib.rest_pose else ''
                action.asset_data['rest_pose'] = name

        return {'FINISHED'}



class ACTIONLIB_OT_assign_rest_pose(Operator):
    bl_idname = "actionlib.mark_as_rest_pose"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Mark As Rest Pose'
    bl_description = 'Mark Pose as Rest Pose'

    @classmethod
    def poll(cls, context: Context) -> bool:
        if context.area.ui_type == 'ASSETS':
            return True

    def execute(self, context: Context) -> Set[str]:
        active_action = context.active_file
        context.scene.actionlib.rest_pose = context.id
        
        print(f"'{active_action.name.title()}' marked as Rest Pose.")
        return {'FINISHED'}


class ACTIONLIB_OT_open_blendfile(Operator):
    bl_idname = "actionlib.open_blendfile"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Set Paths'
    bl_description = 'Open Containing File'
    
    replace_local : BoolProperty(default=False)

    @classmethod
    def poll(cls, context):
        if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data):
            return False

        sp = context.space_data
        if sp.params.asset_library_ref == 'LOCAL':
            return False

        return True

    def execute(self, context: Context) -> Set[str]:
        asset_path = get_asset_source(replace_local=self.replace_local)
        
        cmd = get_bl_cmd(
            blender=bpy.app.binary_path,
            blendfile=str(asset_path),
        )
        subprocess.Popen(cmd)
        return {'FINISHED'}




#LIBRARY_ITEMS = []
'''
def callback_operator(modal_func, operator, override={}):

    def wrap(self, context, event):
        ret, = retset = modal_func(self, context, event)
        if ret in {'FINISHED'}:

            with context.temp_override(**override):
                callback()

        return retset
    return wrap
'''


class ACTIONLIB_OT_make_custom_preview(Operator):
    bl_idname = "actionlib.make_custom_preview"
    bl_label = "Custom Preview"
    bl_description = "Set a camera to preview an asset"

    def modal(self, context, event):
        prefs = get_addons_prefs()

        if not prefs.preview_modal:
            with context.temp_override(area=self.source_area, region=self.source_area.regions[-1]):
                bpy.ops.actionlib.store_anim_pose("INVOKE_DEFAULT", clear_previews=False, **prefs.add_asset_dict)
            return {"FINISHED"}

        return {'PASS_THROUGH'}

    def invoke(self, context, event):

        self.source_area = bpy.context.area

        view3d = get_viewport()
        with context.temp_override(area=view3d, region=view3d.regions[-1], window=context.window):
            # To close the popup
            bpy.ops.screen.screen_full_area()
            bpy.ops.screen.back_to_previous()
        
            view3d = get_viewport()
            with context.temp_override(area=view3d, region=view3d.regions[-1], window=context.window):
                bpy.ops.assetlib.make_custom_preview('INVOKE_DEFAULT', modal=True)

        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


def get_preview_items(self, context):
    prefs = get_addon_prefs()
    return sorted([(k, k, '', v.icon_id, index) for index, (k, v) in enumerate(prefs.previews.items())], reverse=True)
    


class ACTIONLIB_OT_store_anim_pose(Operator):
    bl_idname = "actionlib.store_anim_pose"
    bl_label = "Add Action to the current library"
    bl_description = "Store current pose/anim to local library"

    warning: StringProperty(name='')
    path: StringProperty(name='Path')
    catalog: StringProperty(name='Catalog', update=asset_warning_callback, options={'TEXTEDIT_UPDATE'})
    name: StringProperty(name='Name', update=asset_warning_callback, options={'TEXTEDIT_UPDATE'})
    action_type: EnumProperty(name='Type', items=[('POSE', 'Pose', ''), ('ANIMATION', 'Animation', '')])
    frame_start: IntProperty(name="Frame Start")
    frame_end: IntProperty(name="Frame End")
    tags: StringProperty(name='Tags', description='Tags need to separate with a comma (,)')
    description: StringProperty(name='Description')
    preview : EnumProperty(items=get_preview_items)
    clear_previews : BoolProperty(default=True)
    store_library: StringProperty(name='Store Library')
    
    @classmethod
    def poll(cls, context: Context) -> bool:
        ob = context.object
        if not ob:
            cls.poll_message_set(f'You have no active object')
            return False

        if not ob.type == 'ARMATURE':
            cls.poll_message_set(f'Active object {ob.name} is not of type ARMATURE')
            return False

        if not ob.animation_data or not ob.animation_data.action:
            cls.poll_message_set(f'Active object {ob.name} has no action or keyframes')
            return False

        return True 

    # def draw_tags(self, asset, layout):
    #     row = layout.row()
    #     row.template_list("ASSETBROWSER_UL_metadata_tags", "asset_tags", asset.asset_data, "tags",
    #                       asset.asset_data, "active_tag", rows=4)

    #     col = row.column(align=True)
    #     col.operator("asset.tag_add", icon='ADD', text="")
    #     col.operator("asset.tag_remove", icon='REMOVE', text="")

    def to_dict(self):
        keys = ("catalog", "name", "action_type", "frame_start", "frame_end", "tags", "description", "store_library")
        return {k : getattr(self, k) for k in keys}

    def draw(self, context):
        layout = self.layout
        layout.separator()
        prefs = get_addon_prefs()

        split = layout.split(factor=0.39, align=True)
        #row = split.row(align=False)
        #split.use_property_split = False
        split.alignment = 'RIGHT'

        split.label(text='Preview')

        sub = split.row(align=True)
        sub.template_icon_view(self, "preview", show_labels=False)
        sub.separator()
        sub.operator("actionlib.make_custom_preview", icon='RESTRICT_RENDER_OFF', text='')

        prefs.add_asset_dict.clear()
        prefs.add_asset_dict.update(self.to_dict())

        sub.label(icon='BLANK1')
        #layout.ui_units_x = 50

        #row = layout.row(align=True)
        layout.use_property_split = True
        
        if self.current_library.merge_libraries:
            layout.prop(self.current_library, 'store_library', expand=False)

        #row.label(text='Action Name')
        layout.prop(self, "action_type", text="Type", expand=True)
        if self.action_type == 'ANIMATION':
            col = layout.column(align=True)
            col.prop(self, "frame_start")
            col.prop(self, "frame_end", text='End')

        layout.prop(self, "catalog", text="Catalog")
        layout.prop(self, "name", text="Name")

        #layout.separator()
        #self.draw_tags(self.asset_action, layout)
        layout.prop(self, 'tags')
        layout.prop(self, 'description', text='Description')
        #layout.prop(prefs, 'author', text='Author')

        #layout.prop()

        layout.separator()
        col = layout.column()
        col.use_property_split = False
        #row.enabled = False

        if self.path:
            col.label(text=self.path)

        if self.warning:
            col.label(icon='ERROR', text=self.warning)

    def set_action_type(self):
        ob = bpy.context.object
        action = ob.animation_data.action

        bones = [b.name for b in bpy.context.selected_pose_bones_from_active_object]

        keyframes_selected = get_keyframes(action, selected=True, includes=bones)
        self.frame_start = min(keyframes_selected)
        self.frame_end = max(keyframes_selected)

        self.action_type = 'POSE'
        if (self.frame_start != self.frame_end):
            self.action_type = 'ANIMATION'

    def invoke(self, context, event):
        prefs = get_addon_prefs()
        ob = context.object

        self.asset_action = ob.animation_data.action.copy()
        self.asset_action.asset_mark()
        self.area = context.area
        self.current_library = get_active_library()

        if self.store_library:
            self.current_library.store_library = self.store_library
        else:
            lib = self.current_library.library_type.get_active_asset_library()
            if lib.name:
                self.current_library.store_library = lib.name
                self.store_library = lib.name

        #lib = self.current_library
        self.tags = ''


        #print(self, self.library_items)
        catalog_item = lib.catalog.active_item

        if catalog_item:
            self.catalog = catalog_item.path #get_active_catalog()

        self.set_action_type()

        if self.clear_previews:
            prefs.previews.clear()

            view3d = get_viewport()
            with context.temp_override(area=view3d, region=view3d.regions[-1]):
                bpy.ops.assetlib.make_custom_preview('INVOKE_DEFAULT')
        
        else:
            preview_items = get_preview_items(self, context)
            if preview_items:
                self.preview = preview_items[0][0]

        return context.window_manager.invoke_props_dialog(self, width=350)

    def action_to_asset(self, action):
        #action.asset_mark()
        prefs = get_addon_prefs()
        action.name = self.name
        action.asset_generate_preview()

        # Remove Keyframes   
        bones = [b.name for b in bpy.context.selected_pose_bones_from_active_object]   
        clean_action(
            action=action,
            frame_start=self.frame_start,
            frame_end=self.frame_end,
            excludes=['world', 'walk'],
            includes=bones,
        )

        ## Define Tags
        tags = [t.strip() for t in self.tags.split(',') if t]
        tag_range = f'f{self.frame_start}'
        is_single_frame = True
        if self.action_type == 'ANIM':
            is_single_frame = False
            tag_range = f'f{self.frame_start}-f{self.frame_end}'

        tags.append(self.action_type)
        tags.append(tag_range)

        for tag in tags:
            action.asset_data.tags.new(tag)

        action.asset_data['is_single_frame'] = is_single_frame
        action.asset_data['rig'] = bpy.context.object.name

        action.asset_data.description = self.description
        action.asset_data.author = prefs.author

        col = get_overriden_col(bpy.context.object)
        if col:
            action.asset_data['col'] = col.name
        
        return action

    def render_animation(self, video_path):
        ctx = bpy.context
        scn = ctx.scene
        vl = ctx.view_layer
        area = get_view3d_persp()
        space = area.spaces.active

        attrs = [
            (scn, 'use_preview_range', True),
            (scn, 'frame_preview_start', self.frame_start),
            (scn, 'frame_preview_end', self.frame_end),
            (scn.render, 'resolution_percentage', 100),
            (space.overlay, 'show_overlays', False),
            (space.region_3d, 'view_perspective', 'CAMERA'),
            (scn.render, 'resolution_x', 1280),
            (scn.render, 'resolution_y', 720),
            (scn.render.image_settings, 'file_format', 'FFMPEG'),
            (scn.render.ffmpeg, 'format', 'QUICKTIME'),
            (scn.render.ffmpeg, 'codec', 'H264'),
            (scn.render.ffmpeg, 'ffmpeg_preset', 'GOOD'),
            (scn.render.ffmpeg, 'constant_rate_factor', 'HIGH'),
            (scn.render.ffmpeg, 'gopsize', 12),
            (scn.render, 'filepath', str(video_path)),
        ]

        if self.action_type == "ANIMATION":
            with attr_set(preview_attrs+video_attrs):
                with ctx.temp_override(area=area):
                    bpy.ops.render.opengl(animation=True)

    def save_pose_preview(self):
        pass

    def refresh(self, area):
        bpy.ops.asset.library_refresh({"area": area, 'region': area.regions[3]})
        #space_data.activate_asset_by_id(asset, deferred=deferred)

    def execute(self, context: Context):    

        scn = context.scene
        vl = context.view_layer
        ob = context.object

        prefs = get_addon_prefs()

        lib = self.current_library
        if lib.merge_libraries:
            lib = prefs.libraries[self.current_library.store_library]
        
        lib_type = lib.library_type
        
        asset_path = lib_type.get_asset_path(name=self.name, catalog=self.catalog)
        img_path = lib_type.get_image_path(name=self.name, catalog=self.catalog, filepath=asset_path)
        video_path = lib_type.get_video_path(name=self.name, catalog=self.catalog, filepath=asset_path)

        ## Copy Action
        current_action = ob.animation_data.action
        asset_action = self.asset_action
        ob.animation_data.action = asset_action
        vl.update()

        self.action_to_asset(asset_action)

        #lib_type.new_asset()

        #Saving the video
        if self.action_type == "ANIMATION":
            self.render_animation(video_path)
        
        #Saving the preview image
        preview = prefs.previews[self.preview]
        lib_type.write_preview(preview, img_path)

        # Transfert the pixel to the action preview
        pixels = [0] * preview.image_size[0] * preview.image_size[1] * 4
        preview.image_pixels_float.foreach_get(pixels)
        asset_action.preview_ensure().image_pixels_float.foreach_set(pixels)

        lib_type.write_asset(asset=asset_action, asset_path=asset_path)

        asset_data = dict(lib_type.get_asset_data(asset_action), catalog=self.catalog)
        asset_info = lib_type.format_asset_info([asset_data], asset_path=asset_path)

        #print('asset_info')
        #pprint(asset_info)

        diff = [dict(a, operation='ADD') for a in lib_type.flatten_cache([asset_info])]

        ob.animation_data.action = current_action

        asset_action.asset_clear()
        asset_action.use_fake_user = False
        bpy.data.actions.remove(asset_action)

        # TODO Write a proper method for this
        diff_path = Path(bpy.app.tempdir, 'diff.json')

        #diff = [dict(a, operation='ADD') for a in [asset_info])]
        diff_path.write_text(json.dumps(diff, indent=4))

        bpy.ops.assetlib.bundle(name=lib.name, diff=str(diff_path), blocking=True)

        #self.area.tag_redraw()

        self.report({'INFO'}, f'"{self.name}" has been added to the library.')

        return {"FINISHED"}
    

classes = (
    ACTIONLIB_OT_assign_action,
    ACTIONLIB_OT_restore_previous_action,
    #ACTIONLIB_OT_publish,
    ACTIONLIB_OT_apply_anim,
    ACTIONLIB_OT_replace_pose,
    ACTIONLIB_OT_create_anim_asset,
    ACTIONLIB_OT_apply_selected_action,
    ACTIONLIB_OT_edit_action,
    ACTIONLIB_OT_clear_action,
    ACTIONLIB_OT_generate_preview,
    ACTIONLIB_OT_update_action_data,
    ACTIONLIB_OT_assign_rest_pose,
    ACTIONLIB_OT_store_anim_pose,
    ACTIONLIB_OT_make_custom_preview
)

register, unregister = bpy.utils.register_classes_factory(classes)
