import type { Collapsible } from '@chakra-ui/react';
import { Card, Text, Icon, SkeletonCircle, SkeletonText, Span, Tabs, Timeline, Tag, HStack, Stack, Separator, Wrap} from '@chakra-ui/react';
import { HiCheck } from "react-icons/hi"
import dayjs from "dayjs";
import React from "react";
import { BiWrench } from "react-icons/bi";
import { HiMiniMagnifyingGlass } from "react-icons/hi2";
import { IoMdCodeDownload } from "react-icons/io";
import { TbDatabaseEdit } from "react-icons/tb";
import type {PlayApiCommonDetailed} from "../../core/Api";
import { DEAD_QUEUE, QUEUE_STATUS_COMPLETED, QUEUE_STATUS_FAILED, QUEUE_STATUS_QUEUED, type ComponentType, type JsonPlayObject, type LifecycleStep, type PlayMatchResult, type ScrobbleResult } from "../../core/Atomic";
import { sortByNewestDate } from "../../core/PlayUtils";
import { capitalizeWords } from "../../core/StringUtils";
import { shortTodayAwareFormat } from "../../core/TimeUtils";
import { activityTransformHasIssue, timelineIconProps, TimelineItemSummaryText } from "../utils/ComponentUtils";
import { ChakraCodeBlockShort } from "./CodeBlock";
import { ErrorAlert } from "./ErrorAlert";
import { MSErrorBoundary } from "./ErrorBoundary";
import { CheckIcon, ExclamationTriangleIcon, TimelineIndicatorIconQueued, XIcon } from "./icons/ChakraIcons";
import { MSCollapsible } from "./MSCollapsible";
import { PlayData } from "./PlayData";
import { ScrobbleActionResult } from "./ScrobbleActionResult";
import { ScrobbleMatchResult } from "./ScrobbleMatchResult";
import { TimelineErrorIcon } from "./timeline/TimelineIcon";
import { diffElements, TransformSteps } from "./TransformSteps";
import { Muted } from "./Typography";
import type { PlayEventPlayStateChange, PlayEventQueueStateChange } from '../../core/PlayEvent';
import { FaInfo } from "react-icons/fa6";
interface ActivityTimelineProps {
activity?: PlayApiCommonDetailed
collapsibleOpen?: boolean,
componentType?: ComponentType
componentName?: string
}
const indicatorProps: Collapsible.TriggerProps = {};//{alignItems: "anchor-center"};
const TimelineLoading = () => (
)
const NewItem = (props: Pick) => {
const {
activity: {
play,
input,
seenAt,
} = {},
collapsibleOpen,
componentType
} = props;
const {
meta: {
source,
} = {},
} = play;
const {
play: original,
data: ogInput
} = input || {};
return (
{componentType === 'source' ? 'Discovered' : 'Recieved'} new Play from {capitalizeWords(source)} at {shortTodayAwareFormat(dayjs(seenAt))}
}
defaultOpen={collapsibleOpen}
timeline
disableUntil="md">
Play
{ogInput !== undefined ? Source Data : null}
{ogInput !== undefined ? (
) : null}
)
}
const TransformsItem = (props: Pick & { steps: LifecycleStep[], original: JsonPlayObject }) => {
const {
steps = [],
collapsibleOpen,
original
} = props;
let transformVerb: string = 'Transformed Play';
const transformIssue = activityTransformHasIssue(steps);
let transformResult: React.JSX.Element | undefined;
if (transformIssue === 'error') {
transformVerb = 'Transforming Play';
transformResult = resulted in an error;
} else if (transformIssue === 'warn') {
transformVerb = 'Transforming Play';
transformResult = resulted in warnings;
}
return (
{transformVerb} using configured Rules for {steps[0].hook} {transformResult}}
unmountOnExit
defaultOpen={collapsibleOpen}
timeline>
)
}
const ScrobbleMatchItem = (props: Pick & { match: PlayMatchResult, componentName?: string }) => {
const {
match,
match: {
closestMatchedPlay: {
meta
} = {}
} = {},
componentName = 'service',
collapsibleOpen
} = props;
let fromSource: React.JSX.Element | undefined;
if(match.match && meta !== undefined) {
fromSource = from {meta.parsedFrom === 'history' ? capitalizeWords(componentName) : 'MS Database'}
}
return (
Found {match.match ? a duplicate Scrobble : 'no duplicate Scrobbles'}{fromSource}}
defaultOpen={collapsibleOpen}
disableUntil="md"
unmountOnExit
timeline>
)
}
const ScrobbleResponseItem = (props: Pick & { scrobble: ScrobbleResult, componentName?: string }) => {
const {
scrobble: {
payload,
error,
warnings = []
} = {},
scrobble,
collapsibleOpen,
componentName = 'downstream service'
} = props;
let scrobbleSummary: React.JSX.Element;
const scrobbleIconProps: Record = {
color: 'green.focusRing'
};
if (payload !== undefined) {
if (error !== undefined) {
scrobbleSummary = Scrobble attempt to {capitalizeWords(componentName)} resulted in an error.
} else if (warnings.length > 0) {
scrobbleSummary = Scrobbled to {capitalizeWords(componentName)} but response has warnings.
} else {
scrobbleSummary = Scrobbled to {capitalizeWords(componentName)} successfully.;
}
}
return (
{error !== undefined ? : (
)}
)
}
const RetryChip = (props: { count: number }) => (
Attempt: {props.count}
)
const BoolChip = (props: { text: string, check: boolean }) => (
{props.text}
{props.check ? : }
)
const DupeChip = (props: { check: boolean }) => ;
const TransformChip = (props: { check: boolean }) =>
const CacheChip = (props: { check: boolean }) =>
const QueueTimelineItem = (props: {queueState: PlayEventQueueStateChange, collapsibleOpen: boolean}) => {
const {
queueState: {
data: {
queueStatus,
queueName,
error,
retries,
context,
},
createdAt,
} = {},
collapsibleOpen,
} = props;
const {
dupeCheck,
transform,
useCache,
reason
} = context ?? {};
let indicator: React.JSX.Element,
text: React.JSX.Element,
title: React.JSX.Element;
const contextHints: React.JSX.Element[] = [];
const tags: React.JSX.Element[] = [];
if(retries !== undefined && retries > 0) {
tags.push();
}
if(dupeCheck !== undefined) {
tags.push()
}
if(transform !== undefined) {
tags.push()
}
if(useCache !== undefined) {
tags.push()
}
if(tags.length > 0) {
contextHints.push({tags})
}
if(reason !== undefined) {
contextHints.push(Reason - {reason});
}
switch(queueStatus) {
case QUEUE_STATUS_QUEUED:
indicator = ;
text = {queueName === DEAD_QUEUE ? 'Dead ' : ''}Queued at {shortTodayAwareFormat(dayjs(createdAt))};
break;
case QUEUE_STATUS_COMPLETED:
indicator = ;
text = {queueName === DEAD_QUEUE ? 'Dead ' : ''}Queue finished processing at {shortTodayAwareFormat(dayjs(createdAt))};
break;
case QUEUE_STATUS_FAILED:
indicator = ;
text = {queueName === DEAD_QUEUE ? 'Dead ' : ''}Queue failed at {shortTodayAwareFormat(dayjs(createdAt))};
}
if(error !== undefined && error !== null && typeof error === 'object' && Object.keys(error).length > 0) {
title = (
)
} else {
title = text;
}
return (
{indicator}
{title}
{contextHints.length > 0 ? }>{contextHints} : undefined}
);
}
const StateChangeItem = (props: {event: PlayEventPlayStateChange, collapsibleOpen: boolean}) => {
const {
event: {
data: {
state,
error,
reason
},
createdAt,
} = {},
collapsibleOpen,
} = props;
let color: string;
switch (state) {
case 'discarded':
case 'duped':
color = 'orange';
break;
case 'discovered':
case 'scrobbled':
color = 'green';
break;
case 'failed':
color = 'red';
break;
case 'queued':
color = 'gray';
break;
}
const text = State was changed to {capitalizeWords(state)} at {shortTodayAwareFormat(dayjs(createdAt))};
let content: React.JSX.Element;
if(error !== null && error !== undefined) {
content = (
{reason !== undefined && reason !== null ? {reason} : null}
)
} else {
content = (<>
{text}
{reason !== undefined && reason !== null ? {reason} : null}
>);
}
return (
{content}
);
}
export const ActivityTimeline = (props: ActivityTimelineProps) => {
if(props.activity === undefined) {
return ;
}
const {
activity:{
input,
events = [],
} = {},
collapsibleOpen,
componentType,
componentName
} = props;
const {
play: original,
} = input || {};
events.sort((a, b) => sortByNewestDate(b.createdAt, a.createdAt));
const timelineElements: React.JSX.Element[] = [
];
let lastTransformedPlay = original;
for(const event of events) {
switch(event.eventName) {
case 'transform': {
//const d: TransformStepsTimelineData = {id: 'transform-steps', dt: dayjs(event.data[0].createdAt), steps: event.data, original: lastTransformedPlay};
timelineElements.push();
const [__, finalPlay] = diffElements(lastTransformedPlay, event.data);
lastTransformedPlay = finalPlay;
} break;
case 'queueStateChange': {
timelineElements.push();
} break;
case 'dupeCheck': {
timelineElements.push();
} break;
case 'scrobbleResult':
timelineElements.push();
break;
case 'playStateChange':
timelineElements.push()
}
}
return (
{timelineElements}
)
}