bl_info = {
    "name": "Tangent Cone Generator",
    "author": "AI",
    "version": (1, 9, 0),
    "blender": (5, 0, 0),
    "location": "View3D > Sidebar > Tangent Cone",
    "description": "最短距離の赤矢印を追加。2種類の矢印の長さの計算・コピー機能を搭載",
    "category": "Add Mesh",
}

import bpy
import bmesh
import math
import webbrowser
import random
from mathutils import Vector, Matrix
from bpy.app.handlers import persistent

# ==========================================
# ユーザー設定用パラメーター (ここを書き換えるだけで初期値やリンクを変更できます)
# ==========================================

# ▼ リンク一覧(辞書化)
ADDON_LINKS = {
    "20261003 円錐頂点矢印 と 球体_002": "<https://app.notion.com/p/20261003-3ed691cbf90680a187f7d9c5ea268c2b>",
    "338 Sphere Cam": "<https://app.notion.com/p/338-Sphere-Cam-3c0347b3454a80abbb61ff2cd585792c>",
    "posfie": "<https://posfie.com/@timekagura/t/zionad2022?sort=0>",
    "zionadchat": "<https://x.com/zionadchat>",
    "背後霊カメラ 最新版リンク": "<https://app.notion.com/p/Cam-blender-39f347b3454a806490a6f0107c2347dd>",
    "blender コンテナ 20260713": "<https://note.com/zionadmillion/n/n9a47d88fe7b4>",
    "骨格立方体": "<https://app.notion.com/p/351-2017-3cb347b3454a8086b15fec2f69408bd5>",
}

# ▼ 形状の初期値
INIT_APEX_LOC   = (0.0, 0.0, 10.0)
INIT_RADIUS     = 1.0
INIT_SEGMENTS   = 128
INIT_DRAW_MODE  = 'CONE'  # 'CONE' (円錐) または 'ARROWS' (矢印)
INIT_ARROW_CNT  = 4       # 矢印の初期本数 (1〜12)

# ▼ マテリアルの初期値
INIT_SPHERE_COLOR      = (0.2, 0.6, 1.0)
INIT_SPHERE_ALPHA      = 0.35
INIT_CONE_COLOR        = (1.0, 0.5, 0.1)
INIT_CONE_ALPHA        = 0.25

# ▼ 最短距離矢印の初期値
INIT_SHOW_SHORT_ARROW  = True
INIT_SHORT_ARROW_COLOR = (1.0, 0.0, 0.0) # 初期値: 赤
INIT_SHORT_ARROW_ALPHA = 1.0

# ==========================================
# 起動時の自動セットアップ
# ==========================================
@persistent
def setup_workspace_handler(dummy=None):
    def setup():
        if not bpy.context.window_manager:
            return 0.1
        for window in bpy.context.window_manager.windows:
            for area in window.screen.areas:
                if area.type == 'VIEW_3D':
                    for space in area.spaces:
                        if space.type == 'VIEW_3D':
                            space.show_region_ui = True
                            space.shading.type = 'MATERIAL'
        return None
    bpy.app.timers.register(setup, first_interval=0.1)

# ==========================================
# ユーティリティ関数
# ==========================================
def get_or_create_collection(name):
    coll = bpy.data.collections.get(name)
    if not coll:
        coll = bpy.data.collections.new(name)
        bpy.context.scene.collection.children.link(coll)
    return coll

def get_or_create_object(name, coll):
    obj = bpy.data.objects.get(name)
    if not obj:
        mesh = bpy.data.meshes.new(f"{name}_Mesh")
        obj = bpy.data.objects.new(name, mesh)
        coll.objects.link(obj)
    else:
        if obj.name not in coll.objects:
            coll.objects.link(obj)
        if not obj.data or not isinstance(obj.data, bpy.types.Mesh):
            obj.data = bpy.data.meshes.new(f"{name}_Mesh")
    return obj

def assign_material(obj, name, color, alpha, blend='BLEND'):
    mat = bpy.data.materials.get(name)
    if not mat:
        mat = bpy.data.materials.new(name=name)
        mat.use_nodes = True
    
    mat.blend_method = blend
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    if bsdf:
        bsdf.inputs["Base Color"].default_value = (*color[:3], 1.0)
        if "Alpha" in bsdf.inputs:
            bsdf.inputs["Alpha"].default_value = alpha
        if "Transmission Weight" in bsdf.inputs:
            bsdf.inputs["Transmission Weight"].default_value = 0.1
            
    if not obj.data.materials:
        obj.data.materials.append(mat)
    else:
        obj.data.materials[0] = mat

def get_plane_eq(N, Cc):
    nx, ny, nz = N.x, N.y, N.z
    d = N.dot(Cc)
    terms = []
    if abs(nx) > 1e-4: terms.append(f"{nx:.2f}x")
    if abs(ny) > 1e-4: terms.append(f"{ny:.2f}y")
    if abs(nz) > 1e-4: terms.append(f"{nz:.2f}z")
    if not terms: terms.append("0")
    return " + ".join(terms).replace("+ -", "- ") + f" = {d:.2f}"

def get_sphere_eq(Cc, rc):
    def fmt_term(axis, val):
        if abs(val) < 1e-4: return f"{axis}^2"
        if val < 0: return f"({axis} + {abs(val):.2f})^2"
        return f"({axis} - {val:.2f})^2"
    return f"{fmt_term('x', Cc.x)} + {fmt_term('y', Cc.y)} + {fmt_term('z', Cc.z)} = {(rc*rc):.2f}"

# ==========================================
# リアルタイム生成コアロジック
# ==========================================
def update_tangent_cone_logic(self, context=None):
    if not getattr(self, "is_calculated", False):
        return False

    P = Vector(self.apex_loc)
    C = Vector((0.0, 0.0, 0.0))
    R = self.radius
    segments = self.segments
    V = P - C
    d = V.length

    if d <= R:
        self.res_error = "エラー: 頂点が球の内部または表面にあります"
        return False
    self.res_error = ""

    zc = (R * R) / d
    rc = R * math.sqrt(d * d - R * R) / d
    N = V.normalized()
    Cc = C + N * zc
    cone_h = d - zc
    
    # 長さの計算
    len_tangent = math.sqrt(d * d - R * R)
    len_shortest = d - R

    tmp = Vector((1, 0, 0)) if abs(N.x) < 0.9 else Vector((0, 1, 0))
    U = N.cross(tmp).normalized()
    W = N.cross(U).normalized()

    circle_verts = []
    for i in range(segments):
        theta = 2 * math.pi * i / segments
        circle_verts.append(Cc + rc * (U * math.cos(theta) + W * math.sin(theta)))

    coll = get_or_create_collection("Tangent Cone Setup")

    # --- 矢印生成用の共通関数 ---
    def add_arrow(bm_mesh, P_start, P_end, scale_R):
        vec = P_end - P_start
        L = vec.length
        if L < 1e-4: return
        dir = vec.normalized()
        rot_mat = Vector((0,0,1)).rotation_difference(dir).to_matrix().to_4x4()
        
        r_shaft = scale_R * 0.02
        r_head = scale_R * 0.06
        L_shaft = L * 0.85
        L_head = L * 0.15
        
        ret_shaft = bmesh.ops.create_cone(bm_mesh, cap_ends=True, cap_tris=False, segments=16, radius1=r_shaft, radius2=r_shaft, depth=L_shaft)
        bmesh.ops.transform(bm_mesh, matrix=rot_mat, verts=ret_shaft['verts'])
        bmesh.ops.transform(bm_mesh, matrix=Matrix.Translation(P_start + dir*(L_shaft/2)), verts=ret_shaft['verts'])
        
        ret_head = bmesh.ops.create_cone(bm_mesh, cap_ends=True, cap_tris=False, segments=16, radius1=r_head, radius2=0.0, depth=L_head)
        bmesh.ops.transform(bm_mesh, matrix=rot_mat, verts=ret_head['verts'])
        bmesh.ops.transform(bm_mesh, matrix=Matrix.Translation(P_start + dir*(L_shaft + L_head/2)), verts=ret_head['verts'])

    # 1. Base Sphere
    obj_sphere = get_or_create_object("Sphere_Base", coll)
    bm_sphere = bmesh.new()
    bmesh.ops.create_uvsphere(bm_sphere, u_segments=64, v_segments=32, radius=R)
    for f in bm_sphere.faces: f.smooth = True
    bm_sphere.to_mesh(obj_sphere.data)
    bm_sphere.free()
    assign_material(obj_sphere, "Mat_Sphere", self.sphere_color, self.sphere_alpha)

    # 2. Tangent Cone または 矢印(Arrows)
    obj_cone = get_or_create_object("Cone_Tangent", coll)
    bm_cone = bmesh.new()
    
    if self.draw_mode == 'CONE':
        apex_v = bm_cone.verts.new(P)
        cv = [bm_cone.verts.new(p) for p in circle_verts]
        bm_cone.verts.ensure_lookup_table()
        for i in range(segments):
            bm_cone.faces.new((apex_v, cv[i], cv[(i+1)%segments]))
    else:
        arrows = self.arrow_count
        for i in range(arrows):
            theta = 2 * math.pi * i / arrows
            v_target = Cc + rc * (U * math.cos(theta) + W * math.sin(theta))
            add_arrow(bm_cone, P, v_target, R)

    for f in bm_cone.faces: f.smooth = True
    bm_cone.to_mesh(obj_cone.data)
    bm_cone.free()
    assign_material(obj_cone, "Mat_Cone", self.cone_color, self.cone_alpha)

    # 3. Contact Circle
    obj_circle = get_or_create_object("ContactCircle", coll)
    bm_circle = bmesh.new()
    cv_circ = [bm_circle.verts.new(p) for p in circle_verts]
    bm_circle.verts.ensure_lookup_table()
    for i in range(segments):
        bm_circle.edges.new((cv_circ[i], cv_circ[(i+1)%segments]))
    bm_circle.to_mesh(obj_circle.data)
    bm_circle.free()
    obj_circle.show_wire = True
    assign_material(obj_circle, "Mat_Circle", (1.0, 0.05, 0.05), 1.0, blend='OPAQUE')

    # 4. Spherical Cap
    obj_cap = get_or_create_object("SphericalCap", coll)
    bm_cap = bmesh.new()
    bmesh.ops.create_uvsphere(bm_cap, u_segments=64, v_segments=32, radius=R)
    bm_cap.verts.ensure_lookup_table()
    to_remove = [v for v in bm_cap.verts if (v.co - Cc).dot(N) < -1e-6]
    bmesh.ops.delete(bm_cap, geom=to_remove, context='VERTS')
    for f in bm_cap.faces: f.smooth = True
    bm_cap.to_mesh(obj_cap.data)
    bm_cap.free()
    assign_material(obj_cap, "Mat_Cap", self.sphere_color, self.sphere_alpha)

    # 5. Shortest Arrow (最短距離矢印)
    obj_short = get_or_create_object("ShortestArrow", coll)
    bm_short = bmesh.new()
    if self.show_short_arrow:
        v_target_short = C + N * R
        add_arrow(bm_short, P, v_target_short, R)
        for f in bm_short.faces: f.smooth = True
    bm_short.to_mesh(obj_short.data)
    bm_short.free()
    assign_material(obj_short, "Mat_ShortArrow", self.short_arrow_color, self.short_arrow_alpha)

    # 全メッシュ更新
    for obj in [obj_sphere, obj_cone, obj_circle, obj_cap, obj_short]:
        obj.data.update()

    # 計算結果プロパティの更新
    self.res_apex_z = f"{P.z:.2f}"
    self.res_cone_h = f"{cone_h:.2f}"
    self.res_len_tangent = f"{len_tangent:.2f}"
    self.res_len_shortest = f"{len_shortest:.2f}"
    self.res_circle_z = f"{Cc.z:.2f}"
    self.res_circle_r = f"{rc:.2f}"
    self.res_circle_circum = f"{(2 * math.pi * rc):.2f}"
    self.res_plane_eq = get_plane_eq(N, Cc)
    self.res_sphere_eq = get_sphere_eq(Cc, rc)
    
    return True

# ==========================================
# オペレーター
# ==========================================
class TANGENTCONE_OT_init(bpy.types.Operator):
    bl_idname = "mesh.tangent_cone_init"
    bl_label = "作成 (新規生成)"
    def execute(self, context):
        props = context.scene.tangent_cone_props
        props.is_calculated = True 
        success = update_tangent_cone_logic(props, context)
        if not success:
            props.is_calculated = False 
            self.report({'WARNING'}, props.res_error)
        return {'FINISHED'}

class TANGENTCONE_OT_detach(bpy.types.Operator):
    bl_idname = "mesh.tangent_cone_detach"
    bl_label = "切り離し (独立化)"
    bl_description = "現在の生成物を独立させ、マテリアルの色なども固定して保持します"
    def execute(self, context):
        props = context.scene.tangent_cone_props
        suffix = f"_Fixed_{random.randint(1000, 9999)}"

        names = ["Sphere_Base", "Cone_Tangent", "ContactCircle", "SphericalCap", "ShortestArrow"]
        mat_names = ["Mat_Sphere", "Mat_Cone", "Mat_Circle", "Mat_Cap", "Mat_ShortArrow"]

        coll = bpy.data.collections.get("Tangent Cone Setup")
        if not coll:
            self.report({'WARNING'}, "切り離す対象が見つかりません。")
            props.is_calculated = False
            return {'CANCELLED'}

        fixed_coll = bpy.data.collections.get("Tangent Cone Fixed")
        if not fixed_coll:
            fixed_coll = bpy.data.collections.new("Tangent Cone Fixed")
            context.scene.collection.children.link(fixed_coll)

        for name in names:
            obj = bpy.data.objects.get(name)
            if obj:
                if coll.name in [c.name for c in obj.users_collection]:
                    coll.objects.unlink(obj)
                if fixed_coll.name not in [c.name for c in obj.users_collection]:
                    fixed_coll.objects.link(obj)

                obj.name = name + suffix
                if obj.data:
                    obj.data.name = obj.data.name + suffix
                    
                if obj.data.materials:
                    for slot in obj.material_slots:
                        if slot.material and slot.material.name in mat_names:
                            slot.material.name = slot.material.name + suffix

        props.is_calculated = False
        self.report({'INFO'}, f"切り離し完了。設定を変えて新しく「作成」できます。")
        return {'FINISHED'}

class TANGENTCONE_OT_copy_text(bpy.types.Operator):
    bl_idname = "wm.copy_tangent_cone_text"
    bl_label = "Copy"
    text_to_copy: bpy.props.StringProperty()
    def execute(self, context):
        context.window_manager.clipboard = self.text_to_copy
        self.report({'INFO'}, f"コピーしました: {self.text_to_copy}")
        return {'FINISHED'}

class TANGENTCONE_OT_copy_all(bpy.types.Operator):
    bl_idname = "wm.copy_tangent_cone_all"
    bl_label = "全結果をコピー"
    def execute(self, context):
        props = context.scene.tangent_cone_props
        lines = [
            f"【計算結果】",
            f"頂点のZ座標: {props.res_apex_z}",
            f"円錐の高さ: {props.res_cone_h}",
            f"接線矢印の長さ(母線): {props.res_len_tangent}",
            f"最短矢印の長さ(球表面へ): {props.res_len_shortest}",
            f"接円のZ座標: {props.res_circle_z}",
            f"接円の半径: {props.res_circle_r}",
            f"接円の円周: {props.res_circle_circum}",
            f"",
            f"【接する円の方程式 (連立)】",
            f"平面: {props.res_plane_eq}",
            f"球面: {props.res_sphere_eq}",
        ]
        context.window_manager.clipboard = "\n".join(lines)
        self.report({'INFO'}, "すべての結果をコピーしました")
        return {'FINISHED'}

class TANGENTCONE_OT_open_url(bpy.types.Operator):
    bl_idname = "wm.tangent_cone_open_url"
    bl_label = "リンクを開く"
    url: bpy.props.StringProperty()
    def execute(self, context):
        if self.url: webbrowser.open(self.url)
        return {'FINISHED'}

class TANGENTCONE_OT_remove_addon(bpy.types.Operator):
    bl_idname = "wm.tangent_cone_remove_addon"
    bl_label = "アドオン削除"
    def execute(self, context):
        unregister()
        return {'FINISHED'}

# ==========================================
# プロパティ設定
# ==========================================
class TangentConeProperties(bpy.types.PropertyGroup):
    apex_loc: bpy.props.FloatVectorProperty(name="Apex (X,Y,Z)", default=INIT_APEX_LOC, subtype='XYZ', update=update_tangent_cone_logic)
    radius: bpy.props.FloatProperty(name="Sphere Radius", default=INIT_RADIUS, min=0.001, update=update_tangent_cone_logic)
    segments: bpy.props.IntProperty(name="Segments", default=INIT_SEGMENTS, min=16, max=512, update=update_tangent_cone_logic)
    
    draw_mode: bpy.props.EnumProperty(
        name="描画モード",
        items=[('CONE', "円錐スカート", ""), ('ARROWS', "矢印 (放射状)", "")],
        default=INIT_DRAW_MODE, update=update_tangent_cone_logic
    )
    arrow_count: bpy.props.IntProperty(name="矢印の本数", default=INIT_ARROW_CNT, min=1, max=12, update=update_tangent_cone_logic)
    
    show_short_arrow: bpy.props.BoolProperty(name="最短距離矢印を表示", default=INIT_SHOW_SHORT_ARROW, update=update_tangent_cone_logic)

    sphere_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=INIT_SPHERE_COLOR, min=0.0, max=1.0, update=update_tangent_cone_logic)
    sphere_alpha: bpy.props.FloatProperty(name="透明度", default=INIT_SPHERE_ALPHA, min=0.0, max=1.0, update=update_tangent_cone_logic)
    
    cone_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=INIT_CONE_COLOR, min=0.0, max=1.0, update=update_tangent_cone_logic)
    cone_alpha: bpy.props.FloatProperty(name="透明度", default=INIT_CONE_ALPHA, min=0.0, max=1.0, update=update_tangent_cone_logic)
    
    short_arrow_color: bpy.props.FloatVectorProperty(name="色", subtype='COLOR', default=INIT_SHORT_ARROW_COLOR, min=0.0, max=1.0, update=update_tangent_cone_logic)
    short_arrow_alpha: bpy.props.FloatProperty(name="透明度", default=INIT_SHORT_ARROW_ALPHA, min=0.0, max=1.0, update=update_tangent_cone_logic)
    
    is_calculated: bpy.props.BoolProperty(default=False)
    res_error: bpy.props.StringProperty(default="")
    res_apex_z: bpy.props.StringProperty()
    res_cone_h: bpy.props.StringProperty()
    res_len_tangent: bpy.props.StringProperty()
    res_len_shortest: bpy.props.StringProperty()
    res_circle_z: bpy.props.StringProperty()
    res_circle_r: bpy.props.StringProperty()
    res_circle_circum: bpy.props.StringProperty()
    res_plane_eq: bpy.props.StringProperty()
    res_sphere_eq: bpy.props.StringProperty()

# ==========================================
# UIパネル群
# ==========================================
class VIEW3D_PT_tangent_cone(bpy.types.Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Tangent Cone'
    bl_label = "Tangent Cone Generator"

    def draw(self, context):
        layout = self.layout
        props = context.scene.tangent_cone_props

        # 形状設定
        col = layout.column(align=True)
        col.prop(props, "apex_loc")
        col.prop(props, "radius")
        col.prop(props, "segments")

        layout.separator()
        
        # 描画モードの切り替え
        box_mode = layout.box()
        box_mode.label(text="生成形状の選択", icon='MODIFIER')
        box_mode.prop(props, "draw_mode", expand=True)
        if props.draw_mode == 'ARROWS':
            box_mode.prop(props, "arrow_count")
            
        box_mode.separator()
        box_mode.prop(props, "show_short_arrow", toggle=True, icon='TRACKING_FORWARDS')

        layout.separator()
        
        # マテリアル設定
        box_mat = layout.box()
        box_mat.label(text="マテリアル (動的反映)", icon='MATERIAL')
        
        row_sphere = box_mat.row(align=True)
        row_sphere.label(text="球体:")
        row_sphere.prop(props, "sphere_color", text="")
        row_sphere.prop(props, "sphere_alpha", slider=True)
        
        row_cone = box_mat.row(align=True)
        row_cone.label(text="円錐/接矢印:")
        row_cone.prop(props, "cone_color", text="")
        row_cone.prop(props, "cone_alpha", slider=True)

        if props.show_short_arrow:
            row_short = box_mat.row(align=True)
            row_short.label(text="最短矢印:")
            row_short.prop(props, "short_arrow_color", text="")
            row_short.prop(props, "short_arrow_alpha", slider=True)

        layout.separator()
        
        # 作成 / 切り離し ボタン
        if not props.is_calculated:
            layout.operator("mesh.tangent_cone_init", text="作成 (新規生成)", icon='MESH_CONE')
        else:
            layout.operator("mesh.tangent_cone_detach", text="切り離し (独立化)", icon='UNLINKED')
        
        if props.res_error:
            layout.separator()
            layout.label(text=props.res_error, icon='ERROR')
            return

        # 計算結果
        if props.is_calculated:
            layout.separator()
            box = layout.box()
            box.label(text="【計算結果】", icon='INFO')
            
            def draw_result(parent, label_text, val_text):
                row = parent.row(align=True)
                split = row.split(factor=0.55)
                split.label(text=label_text)
                sub = split.row(align=True)
                sub.label(text=val_text)
                op = sub.operator("wm.copy_tangent_cone_text", text="", icon='COPYDOWN')
                op.text_to_copy = val_text

            col = box.column(align=True)
            draw_result(col, "接線矢印の長さ (母線)", props.res_len_tangent)
            draw_result(col, "最短矢印の長さ (球表面へ)", props.res_len_shortest)
            col.separator()
            
            draw_result(col, "頂点の Z座標", props.res_apex_z)
            draw_result(col, "高さ (頂点〜円)", props.res_cone_h)
            draw_result(col, "接円の Z座標", props.res_circle_z)
            draw_result(col, "接円の半径", props.res_circle_r)
            draw_result(col, "接円の円周長", props.res_circle_circum)
            
            col.separator()
            col.label(text="【接する円の方程式】")
            draw_result(col, "平面", props.res_plane_eq)
            draw_result(col, "球面", props.res_sphere_eq)
            
            col.separator()
            col.operator("wm.copy_tangent_cone_all", text="すべての結果をコピー", icon='DOCUMENTS')

class VIEW3D_PT_tangent_cone_links(bpy.types.Panel):
    bl_label = "Links"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Tangent Cone'

    def draw(self, context):
        layout = self.layout
        for name, url in ADDON_LINKS.items():
            if name and url:
                layout.operator("wm.tangent_cone_open_url", text=name, icon='URL').url = url

class VIEW3D_PT_tangent_cone_remove(bpy.types.Panel):
    bl_label = "Addon Remove"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Tangent Cone'

    def draw(self, context):
        layout = self.layout
        layout.operator("wm.tangent_cone_remove_addon", icon='CANCEL')

# ==========================================
# 登録処理
# ==========================================
classes = (
    TANGENTCONE_OT_init,
    TANGENTCONE_OT_detach,
    TANGENTCONE_OT_copy_text,
    TANGENTCONE_OT_copy_all,
    TANGENTCONE_OT_open_url,
    TANGENTCONE_OT_remove_addon,
    TangentConeProperties,
    VIEW3D_PT_tangent_cone,
    VIEW3D_PT_tangent_cone_links,
    VIEW3D_PT_tangent_cone_remove,
)

def register():
    for cls in classes:
        bpy.utils.register_class(cls)
    bpy.types.Scene.tangent_cone_props = bpy.props.PointerProperty(type=TangentConeProperties)
    
    bpy.app.handlers.load_post.append(setup_workspace_handler)
    setup_workspace_handler()

def unregister():
    for cls in reversed(classes):
        try:
            bpy.utils.unregister_class(cls)
        except:
            pass
    if hasattr(bpy.types.Scene, "tangent_cone_props"):
        del bpy.types.Scene.tangent_cone_props
    
    if setup_workspace_handler in bpy.app.handlers.load_post:
        bpy.app.handlers.load_post.remove(setup_workspace_handler)

if __name__ == "__main__":
    try: unregister()
    except: pass
    register()